zmap-mark-ii/src/zmap.c

1061 lines
33 KiB
C

/*
* ZMap Copyright 2013 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sched.h>
#include <errno.h>
#include <pwd.h>
#include <time.h>
#include <pcap/pcap.h>
#include <json.h>
#include <pthread.h>
#include "../lib/includes.h"
#include "../lib/blocklist.h"
#include "../lib/logger.h"
#include "../lib/random.h"
#include "../lib/util.h"
#include "../lib/xalloc.h"
#include "../lib/pbm.h"
#include "aesrand.h"
#include "constants.h"
#include "ports.h"
#include "zopt.h"
#include "send.h"
#include "recv.h"
#include "state.h"
#include "monitor.h"
#include "get_gateway.h"
#include "filter.h"
#include "summary.h"
#include "utility.h"
#include "output_modules/output_modules.h"
#include "probe_modules/probe_modules.h"
#ifdef PFRING
#include <pfring_zc.h>
static int32_t distrib_func(pfring_zc_pkt_buff *pkt, pfring_zc_queue *in_queue,
void *arg)
{
(void)pkt;
(void)in_queue;
(void)arg;
return 0;
}
#endif
pthread_mutex_t recv_ready_mutex = PTHREAD_MUTEX_INITIALIZER;
int get_num_cores(void) {
return sysconf(_SC_NPROCESSORS_ONLN);
}
typedef struct send_arg {
uint32_t cpu;
sock_t sock;
shard_t *shard;
} send_arg_t;
typedef struct recv_arg {
uint32_t cpu;
} recv_arg_t;
typedef struct mon_start_arg {
uint32_t cpu;
iterator_t *it;
pthread_mutex_t *recv_ready_mutex;
} mon_start_arg_t;
const char *default_help_text =
"By default, ZMap prints out unique, successful "
"IP addresses (e.g., SYN-ACK from a TCP SYN scan) "
"in ASCII form (e.g., 192.168.1.5) to stdout or the specified output "
"file. Internally this is handled by the \"csv\" output module and is "
"equivalent to running zmap --output-module=csv --output-fields=saddr "
"--output-filter=\"success = 1 && repeat = 0\" --no-header-row.";
static void *start_send(void *arg)
{
send_arg_t *s = (send_arg_t *)arg;
log_debug("zmap", "Pinning a send thread to core %u", s->cpu);
set_cpu(s->cpu);
int ret = send_run(s->sock, s->shard);
free(s);
if (ret != EXIT_SUCCESS) {
log_fatal("send", "send_run failed, terminating");
}
return NULL;
}
static void *start_recv(void *arg)
{
recv_arg_t *r = (recv_arg_t *)arg;
log_debug("zmap", "Pinning receive thread to core %u", r->cpu);
set_cpu(r->cpu);
recv_run(&recv_ready_mutex);
return NULL;
}
static void *start_mon(void *arg)
{
mon_start_arg_t *mon_arg = (mon_start_arg_t *)arg;
log_debug("zmap", "Pinning monitor thread to core %u", mon_arg->cpu);
set_cpu(mon_arg->cpu);
monitor_run(mon_arg->it, mon_arg->recv_ready_mutex);
free(mon_arg);
return NULL;
}
static void start_zmap(void)
{
if (zconf.iface == NULL) {
zconf.iface = get_default_iface();
assert(zconf.iface);
log_debug("zmap",
"no interface provided. will use default"
" interface (%s).",
zconf.iface);
}
if (zconf.number_source_ips == 0) {
struct in_addr default_ip;
if (get_iface_ip(zconf.iface, &default_ip) < 0) {
log_fatal("zmap",
"could not detect default IP address for %s."
" Try specifying a source address (-S).",
zconf.iface);
}
zconf.source_ip_addresses[0] = default_ip.s_addr;
zconf.number_source_ips++;
log_debug(
"zmap",
"no source IP address given. will use default address: %s.",
inet_ntoa(default_ip));
}
if (!zconf.gw_mac_set) {
struct in_addr gw_ip;
memset(&gw_ip, 0, sizeof(struct in_addr));
if (get_default_gw(&gw_ip, zconf.iface) < 0) {
log_fatal(
"zmap",
"could not detect default gateway address for %s."
" Try setting default gateway mac address (-G)."
" If this is a newly launched machine, try completing an outgoing network connection (e.g. curl https://zmap.io), and trying again.",
zconf.iface);
}
log_debug("zmap", "found gateway IP %s on %s", inet_ntoa(gw_ip),
zconf.iface);
zconf.gw_ip = gw_ip.s_addr;
memset(&zconf.gw_mac, 0, MAC_ADDR_LEN);
if (get_hw_addr(&gw_ip, zconf.iface, zconf.gw_mac)) {
log_fatal(
"zmap",
"could not detect GW MAC address for %s on %s."
" Try setting default gateway mac address (-G), or run"
" \"arp <gateway_ip>\" in terminal."
" If this is a newly launched machine, try completing an outgoing network connection (e.g. curl https://zmap.io), and trying again.",
inet_ntoa(gw_ip), zconf.iface);
}
zconf.gw_mac_set = 1;
}
log_debug("send", "gateway MAC address %02x:%02x:%02x:%02x:%02x:%02x",
zconf.gw_mac[0], zconf.gw_mac[1], zconf.gw_mac[2],
zconf.gw_mac[3], zconf.gw_mac[4], zconf.gw_mac[5]);
// Initialization
assert(zconf.output_module && "no output module set");
log_debug("zmap", "output module: %s", zconf.output_module->name);
if (zconf.output_module && zconf.output_module->init) {
if (zconf.output_module->init(&zconf, zconf.output_fields,
zconf.output_fields_len)) {
log_fatal(
"zmap",
"output module did not initialize successfully.");
}
}
iterator_t *it = send_init();
if (!it) {
log_fatal("zmap", "unable to initialize sending component");
}
if (zconf.output_module && zconf.output_module->start) {
zconf.output_module->start(&zconf, &zsend, &zrecv);
}
// start threads
uint32_t cpu = 0;
pthread_t *tsend, trecv, tmon;
int r;
if (!zconf.dryrun) {
recv_arg_t *recv_arg = xmalloc(sizeof(recv_arg_t));
recv_arg->cpu = zconf.pin_cores[cpu % zconf.pin_cores_len];
cpu += 1;
r = pthread_create(&trecv, NULL, start_recv, recv_arg);
if (r != 0) {
log_fatal("zmap", "unable to create recv thread");
}
for (;;) {
pthread_mutex_lock(&recv_ready_mutex);
if (zconf.recv_ready) {
pthread_mutex_unlock(&recv_ready_mutex);
break;
}
pthread_mutex_unlock(&recv_ready_mutex);
}
}
#ifdef PFRING
pfring_zc_worker *zw = pfring_zc_run_balancer(
zconf.pf.queues, &zconf.pf.send, zconf.senders, 1,
zconf.pf.prefetches, round_robin_bursts_policy, NULL, distrib_func,
NULL, 0, zconf.pin_cores[cpu & zconf.pin_cores_len]);
cpu += 1;
#endif
tsend = xmalloc(zconf.senders * sizeof(pthread_t));
for (uint8_t i = 0; i < zconf.senders; i++) {
sock_t sock;
if (zconf.dryrun) {
sock = get_dryrun_socket();
} else {
sock = get_socket(i);
}
send_arg_t *arg = xmalloc(sizeof(send_arg_t));
arg->sock = sock;
arg->shard = get_shard(it, i);
arg->cpu = zconf.pin_cores[cpu % zconf.pin_cores_len];
cpu += 1;
int r = pthread_create(&tsend[i], NULL, start_send, arg);
if (r != 0) {
log_fatal("zmap", "unable to create send thread");
exit(EXIT_FAILURE);
}
}
log_debug("zmap", "%d sender threads spawned", zconf.senders);
if (!zconf.dryrun) {
monitor_init();
mon_start_arg_t *mon_arg = xmalloc(sizeof(mon_start_arg_t));
mon_arg->it = it;
mon_arg->recv_ready_mutex = &recv_ready_mutex;
mon_arg->cpu = zconf.pin_cores[cpu % zconf.pin_cores_len];
int r = pthread_create(&tmon, NULL, start_mon, mon_arg);
if (r != 0) {
log_fatal("zmap", "unable to create monitor thread");
exit(EXIT_FAILURE);
}
}
#ifndef PFRING
drop_privs();
#endif
// wait for completion
for (uint8_t i = 0; i < zconf.senders; i++) {
int r = pthread_join(tsend[i], NULL);
if (r != 0) {
log_fatal("zmap", "unable to join send thread");
exit(EXIT_FAILURE);
}
}
log_debug("zmap", "senders finished");
#ifdef PFRING
pfring_zc_kill_worker(zw);
pfring_zc_sync_queue(zconf.pf.send, tx_only);
log_debug("zmap", "send queue flushed");
#endif
// no receiving or monitoring thread is started in dry run mode
if (!zconf.dryrun) {
r = pthread_join(trecv, NULL);
if (r != 0) {
log_fatal("zmap", "unable to join recv thread");
exit(EXIT_FAILURE);
}
if (!zconf.quiet || zconf.status_updates_file) {
pthread_join(tmon, NULL);
if (r != 0) {
log_fatal("zmap",
"unable to join monitor thread");
exit(EXIT_FAILURE);
}
}
}
// finished
if (zconf.metadata_filename) {
json_metadata(zconf.metadata_file);
}
if (zconf.output_module && zconf.output_module->close) {
zconf.output_module->close(&zconf, &zsend, &zrecv);
}
if (zconf.probe_module && zconf.probe_module->close) {
zconf.probe_module->close(&zconf, &zsend, &zrecv);
}
#ifdef PFRING
pfring_zc_destroy_cluster(zconf.pf.cluster);
#endif
log_info("zmap", "completed");
}
#define SET_IF_GIVEN(DST, ARG) \
{ \
if (args.ARG##_given) { \
(DST) = args.ARG##_arg; \
}; \
}
#define SET_BOOL(DST, ARG) \
{ \
if (args.ARG##_given) { \
(DST) = 1; \
}; \
}
int main(int argc, char *argv[])
{
struct gengetopt_args_info args;
struct cmdline_parser_params *params;
params = cmdline_parser_params_create();
params->initialize = 1;
params->override = 0;
params->check_required = 0;
int config_loaded = 0;
if (cmdline_parser_ext(argc, argv, &args, params) != 0) {
exit(EXIT_SUCCESS);
}
if (args.config_given || file_exists(args.config_arg)) {
params->initialize = 0;
params->override = 0;
if (cmdline_parser_config_file(args.config_arg, &args,
params) != 0) {
exit(EXIT_FAILURE);
}
config_loaded = 1;
}
// set defaults before loading in command line arguments
init_empty_global_configuration(&zconf);
// initialize logging. if no log file or log directory are specified
// default to using stderr.
zconf.log_level = args.verbosity_arg;
zconf.log_file = args.log_file_arg;
zconf.log_directory = args.log_directory_arg;
if (args.disable_syslog_given) {
zconf.syslog = 0;
} else {
zconf.syslog = 1;
}
if (zconf.log_file && zconf.log_directory) {
log_init(stderr, zconf.log_level, zconf.syslog, "zmap");
log_fatal("zmap", "log-file and log-directory cannot "
"specified simultaneously.");
}
FILE *log_location = NULL;
if (zconf.log_file) {
log_location = fopen(zconf.log_file, "w");
} else if (zconf.log_directory) {
time_t now;
time(&now);
struct tm *local = localtime(&now);
char path[100];
strftime(path, 100, "zmap-%Y-%m-%dT%H%M%S%z.log", local);
char *fullpath =
xmalloc(strlen(zconf.log_directory) + strlen(path) + 2);
sprintf(fullpath, "%s/%s", zconf.log_directory, path);
log_location = fopen(fullpath, "w");
free(fullpath);
} else {
log_location = stderr;
}
if (!log_location) {
log_init(stderr, zconf.log_level, zconf.syslog, "zmap");
log_fatal("zmap", "unable to open specified log file: %s",
strerror(errno));
}
log_init(log_location, zconf.log_level, zconf.syslog, "zmap");
log_debug("zmap", "zmap main thread started");
if (config_loaded) {
log_debug("zmap", "Loaded configuration file %s",
args.config_arg);
}
if (zconf.syslog) {
log_debug("zmap", "syslog support enabled");
} else {
log_info("zmap", "syslog support disabled");
}
// parse the provided probe and output module s.t. that we can support
// other command-line helpers (e.g. probe help)
log_debug("zmap", "requested ouput-module: %s", args.output_module_arg);
// ZMap's default behavior is to provide a simple file of the unique IP
// addresses that responded successfully. We only use this simple "default"
// mode if none of {output module, output filter, output fields} are set.
zconf.default_mode =
(!(args.output_module_given || args.output_filter_given ||
args.output_fields_given));
if (zconf.default_mode) {
log_info(
"zmap",
"By default, ZMap will output the unique IP addresses "
"of hosts that respond successfully (e.g., SYN-ACK packet). This "
"is equivalent to running ZMap with the following flags: "
"--output-module=csv --output-fields=saddr --output-filter='"
"success=1 && repeat=0' --no-header-row. "
"If you want all responses, explicitly set an output module or "
"set --output-filter=\"\".");
zconf.output_module = get_output_module_by_name("csv");
zconf.output_module_name = strdup("csv");
zconf.no_header_row = 1;
} else if (!args.output_module_given) {
log_debug("zmap", "No output module provided. Will use csv.");
zconf.output_module = get_output_module_by_name("csv");
zconf.output_module_name = strdup("csv");
} else {
zconf.output_module =
get_output_module_by_name(args.output_module_arg);
if (!zconf.output_module) {
log_fatal(
"zmap",
"specified output module (%s) does not exist\n",
args.output_module_arg);
}
zconf.output_module_name = strdup(args.output_module_arg);
}
zconf.probe_module = get_probe_module_by_name(args.probe_module_arg);
if (!zconf.probe_module) {
log_fatal("zmap",
"specified probe module (%s) does not exist\n",
args.probe_module_arg);
exit(EXIT_FAILURE);
}
// check whether the probe module is going to generate dynamic data
// and that the output module can support exporting that data out of
// zmap. If they can't, then quit.
if (zconf.probe_module->output_type == OUTPUT_TYPE_DYNAMIC &&
!zconf.output_module->supports_dynamic_output) {
log_fatal(
"zmap",
"specified probe module (%s) requires dynamic "
"output support, which output module (%s) does not support. "
"Most likely you want to use JSON output.",
args.probe_module_arg, args.output_module_arg);
}
if (args.help_given) {
cmdline_parser_print_help();
printf("\nProbe Module (%s) Help:\n", zconf.probe_module->name);
if (zconf.probe_module->helptext) {
fprintw(stdout, zconf.probe_module->helptext, 80);
} else {
printf("no help text available\n");
}
assert(zconf.output_module && "no output module set");
const char *module_name =
zconf.default_mode ? "Default" : zconf.output_module->name;
printf("\nOutput Module (%s) Help:\n", module_name);
if (zconf.default_mode) {
fprintw(stdout, default_help_text, 80);
} else if (zconf.output_module->helptext) {
fprintw(stdout, zconf.output_module->helptext, 80);
} else {
printf("no help text available\n");
}
exit(EXIT_SUCCESS);
}
if (args.version_given) {
cmdline_parser_print_version();
exit(EXIT_SUCCESS);
}
if (args.list_output_modules_given) {
print_output_modules();
exit(EXIT_SUCCESS);
}
if (args.list_probe_modules_given) {
print_probe_modules();
exit(EXIT_SUCCESS);
}
if (args.iplayer_given) {
zconf.send_ip_pkts = 1;
zconf.gw_mac_set = 1;
memset(zconf.gw_mac, 0, MAC_ADDR_LEN);
}
if (cmdline_parser_required(&args, CMDLINE_PARSER_PACKAGE) != 0) {
exit(EXIT_FAILURE);
}
// now that we know the probe module, let's find what it supports
memset(&zconf.fsconf, 0, sizeof(struct fieldset_conf));
// the set of fields made available to a user is constructed
// of IP header fields + probe module fields + system fields
fielddefset_t *fds = &(zconf.fsconf.defs);
gen_fielddef_set(fds, (fielddef_t *)&(ip_fields), ip_fields_len);
gen_fielddef_set(fds, zconf.probe_module->fields,
zconf.probe_module->numfields);
gen_fielddef_set(fds, (fielddef_t *)&(sys_fields), sys_fields_len);
if (args.list_output_fields_given) {
for (int i = 0; i < fds->len; i++) {
printf("%-15s %6s: %s\n", fds->fielddefs[i].name,
fds->fielddefs[i].type, fds->fielddefs[i].desc);
}
exit(EXIT_SUCCESS);
}
// find the fields we need for the framework
zconf.fsconf.success_index = fds_get_index_by_name(fds, "success");
if (zconf.fsconf.success_index < 0) {
log_fatal("fieldset", "probe module does not supply "
"required success field.");
}
zconf.fsconf.app_success_index =
fds_get_index_by_name(fds, "app_success");
if (zconf.fsconf.app_success_index < 0) {
log_debug("fieldset", "probe module does not supply "
"application success field.");
} else {
log_debug(
"fieldset",
"probe module supplies app_success"
" output field. It will be included in monitor output");
}
zconf.fsconf.classification_index =
fds_get_index_by_name(fds, "classification");
if (zconf.fsconf.classification_index < 0) {
log_fatal("fieldset", "probe module does not supply "
"required packet classification field.");
}
zconf.ignore_invalid_hosts = args.ignore_blocklist_errors_given;
SET_BOOL(zconf.dryrun, dryrun);
SET_BOOL(zconf.quiet, quiet);
SET_BOOL(zconf.no_header_row, no_header_row);
zconf.cooldown_secs = args.cooldown_time_arg;
SET_IF_GIVEN(zconf.output_filename, output_file);
SET_IF_GIVEN(zconf.blocklist_filename, blocklist_file);
SET_IF_GIVEN(zconf.list_of_ips_filename, list_of_ips_file);
SET_IF_GIVEN(zconf.probe_args, probe_args);
SET_IF_GIVEN(zconf.probe_ttl, probe_ttl);
SET_IF_GIVEN(zconf.output_args, output_args);
SET_IF_GIVEN(zconf.iface, interface);
SET_IF_GIVEN(zconf.max_runtime, max_runtime);
SET_IF_GIVEN(zconf.max_results, max_results);
SET_IF_GIVEN(zconf.rate, rate);
SET_IF_GIVEN(zconf.packet_streams, probes);
SET_IF_GIVEN(zconf.status_updates_file, status_updates_file);
SET_IF_GIVEN(zconf.retries, retries);
SET_IF_GIVEN(zconf.max_sendto_failures, max_sendto_failures);
SET_IF_GIVEN(zconf.min_hitrate, min_hitrate);
if (zconf.retries < 0) {
log_fatal("zmap", "Invalid retry count");
}
if (zconf.max_sendto_failures >= 0) {
log_debug("zmap",
"scan will abort if more than %i "
"sendto failures occur",
zconf.max_sendto_failures);
}
if (zconf.min_hitrate > 0.0) {
log_debug("zmap", "scan will abort if hitrate falls below %f",
zconf.min_hitrate);
}
if (args.metadata_file_arg) {
zconf.metadata_filename = args.metadata_file_arg;
if (!strcmp(zconf.metadata_filename, "-")) {
zconf.metadata_file = stdout;
} else {
zconf.metadata_file =
fopen(zconf.metadata_filename, "w");
}
if (!zconf.metadata_file) {
log_fatal("metadata",
"unable to open metadata file (%s): %s",
zconf.metadata_filename, strerror(errno));
}
log_debug("metadata", "metadata will be saved to %s",
zconf.metadata_filename);
}
if (args.user_metadata_given) {
zconf.custom_metadata_str = args.user_metadata_arg;
if (!json_tokener_parse(zconf.custom_metadata_str)) {
log_fatal("metadata",
"unable to parse custom user metadata");
} else {
log_debug("metadata",
"user metadata validated successfully");
}
}
if (args.notes_given) {
zconf.notes = args.notes_arg;
}
// find if zmap wants any specific cidrs scanned instead
// of the entire Internet
zconf.destination_cidrs = args.inputs;
zconf.destination_cidrs_len = args.inputs_num;
if (zconf.destination_cidrs && zconf.blocklist_filename &&
!strcmp(zconf.blocklist_filename, ZMAP_DEFAULT_BLOCKLIST)) {
log_warn(
"blocklist",
"ZMap is currently using the default blocklist located "
"at " ZMAP_DEFAULT_BLOCKLIST
". By default, this blocklist excludes locally "
"scoped networks (e.g. 10.0.0.0/8, 127.0.0.1/8, and 192.168.0.0/16). If you are"
" trying to scan local networks, you can change the default blocklist by "
"editing the default ZMap configuration at " ZMAP_DEFAULT_BLOCKLIST
"."
" If you have modified the default blocklist, you can ignore this message.");
}
SET_IF_GIVEN(zconf.allowlist_filename, allowlist_file);
if (zconf.probe_module->port_args) {
if (args.source_port_given) {
char *dash = strchr(args.source_port_arg, '-');
if (dash) { // range
*dash = '\0';
zconf.source_port_first =
atoi(args.source_port_arg);
enforce_range("starting source-port",
zconf.source_port_first, 0,
0xFFFF);
zconf.source_port_last = atoi(dash + 1);
enforce_range("ending source-port",
zconf.source_port_last, 0,
0xFFFF);
if (zconf.source_port_first >
zconf.source_port_last) {
fprintf(
stderr,
"%s: invalid source port range: "
"last port is less than first port\n",
CMDLINE_PARSER_PACKAGE);
exit(EXIT_FAILURE);
}
} else { // single port
int port = atoi(args.source_port_arg);
enforce_range("source-port", port, 0, 0xFFFF);
zconf.source_port_first = port;
zconf.source_port_last = port;
}
int num_source_ports = (zconf.source_port_last - zconf.source_port_first) + 1;
if (zconf.packet_streams > num_source_ports) {
log_fatal("zmap", "The number of probes sent to each target ip/port (%i) "
"must be smaller than the size of the source port range (%u-%u, size: %i). "
"Otherwise, some generated probe packets will be identical.", zconf.packet_streams,
zconf.source_port_first, zconf.source_port_last,
(zconf.source_port_last - zconf.source_port_first) + 1);
} else if (((float)zconf.packet_streams / (float)num_source_ports) < 0.1) {
log_warn("zmap", "ZMap is configured to use a relatively small number"
" of source ports (fewer than 10x the number of probe packets per target ip/port),"
" which limits the entropy that ZMap has available for "
" validating responses. We recommend that you use a larger port range.");
}
}
if (!args.target_ports_given) {
log_fatal("zmap",
"target ports (-p) required for %s probe",
zconf.probe_module->name);
}
} else {
if (args.target_ports_given) {
log_fatal("zmap",
"Destination port cannot be set for %s probe",
zconf.probe_module->name);
}
}
zconf.ports = xmalloc(sizeof(struct port_conf));
zconf.ports->port_bitmap = bm_init();
if (args.target_ports_given) {
parse_ports(args.target_ports_arg, zconf.ports);
} else {
char *line = strdup("0");
parse_ports(line, zconf.ports);
}
if (args.dedup_method_given) {
if (!strcmp(args.dedup_method_arg, "default")) {
if (zconf.ports->port_count > 1) {
zconf.dedup_method = DEDUP_METHOD_WINDOW;
} else {
zconf.dedup_method = DEDUP_METHOD_FULL;
}
} else if (!strcmp(args.dedup_method_arg, "none")) {
zconf.dedup_method = DEDUP_METHOD_NONE;
} else if (!strcmp(args.dedup_method_arg, "full")) {
zconf.dedup_method = DEDUP_METHOD_FULL;
} else if (!strcmp(args.dedup_method_arg, "window")) {
zconf.dedup_method = DEDUP_METHOD_WINDOW;
} else {
log_fatal(
"dedup",
"Invalid dedup option provided. Legal options are: default, none, full, window.");
}
} else {
if (zconf.ports->port_count > 1) {
zconf.dedup_method = DEDUP_METHOD_WINDOW;
} else {
zconf.dedup_method = DEDUP_METHOD_FULL;
}
}
if (zconf.dedup_method == DEDUP_METHOD_FULL &&
zconf.ports->port_count > 1) {
log_fatal(
"dedup",
"full response de-duplication is not supported for multiple ports");
}
if (zconf.dedup_method == DEDUP_METHOD_WINDOW) {
if (args.dedup_window_size_given) {
zconf.dedup_window_size = args.dedup_window_size_arg;
} else {
zconf.dedup_window_size = 1000000;
}
log_info("dedup",
"Response deduplication method is %s with size %u",
DEDUP_METHOD_NAMES[zconf.dedup_method],
zconf.dedup_window_size);
} else {
log_info("dedup", "Response deduplication method is %s",
DEDUP_METHOD_NAMES[zconf.dedup_method]);
}
// process the list of requested output fields.
if (args.output_fields_given) {
zconf.raw_output_fields = args.output_fields_arg;
} else {
if (zconf.ports->port_count > 1) {
zconf.raw_output_fields = "saddr,sport";
} else {
zconf.raw_output_fields = "saddr";
}
}
// add all fields if wildcard received
if (!strcmp(zconf.raw_output_fields, "*")) {
zconf.output_fields_len = zconf.fsconf.defs.len;
zconf.output_fields =
xcalloc(zconf.fsconf.defs.len, sizeof(const char *));
for (int i = 0; i < zconf.fsconf.defs.len; i++) {
zconf.output_fields[i] =
zconf.fsconf.defs.fielddefs[i].name;
}
fs_generate_full_fieldset_translation(&zconf.fsconf.translation,
&zconf.fsconf.defs);
} else {
split_string(zconf.raw_output_fields,
&(zconf.output_fields_len),
&(zconf.output_fields));
for (int i = 0; i < zconf.output_fields_len; i++) {
log_debug("zmap", "requested output field (%i): %s", i,
zconf.output_fields[i]);
}
// generate a translation that can be used to convert output
// from a probe module to the input for an output module
fs_generate_fieldset_translation(
&zconf.fsconf.translation, &zconf.fsconf.defs,
zconf.output_fields, zconf.output_fields_len);
}
// default filtering behavior is to drop unsuccessful and duplicates
if (zconf.default_mode) {
log_debug(
"filter",
"No output filter specified. Will use default: exclude duplicates and unssuccessful");
} else if (args.output_filter_given &&
strcmp(args.output_filter_arg, "")) {
// Run it through yyparse to build the expression tree
if (!parse_filter_string(args.output_filter_arg)) {
log_fatal("zmap", "Unable to parse filter expression");
}
// Check the fields used against the fieldset in use
if (!validate_filter(zconf.filter.expression,
&zconf.fsconf.defs)) {
log_fatal("zmap", "Invalid filter");
}
zconf.output_filter_str = args.output_filter_arg;
log_debug("filter", "will use output filter %s",
args.output_filter_arg);
} else if (args.output_filter_given) { // (empty filter argument)
log_debug(
"filter",
"Empty output filter provided. ZMap will output all "
"results, including duplicate and non-successful responses.");
} else {
log_info(
"filter",
"No output filter provided. ZMap will output all "
"results, including duplicate and non-successful responses (e.g., "
"RST and ICMP packets). If you want a filter similar to ZMap's "
"default behavior, you can set an output filter similar to the "
"following: --output-filter=\"success=1 && repeat=0\".");
}
if (args.source_ip_given) {
parse_source_ip_addresses(args.source_ip_arg);
}
if (args.gateway_mac_given) {
if (!parse_mac(zconf.gw_mac, args.gateway_mac_arg)) {
fprintf(stderr, "%s: invalid MAC address `%s'\n",
CMDLINE_PARSER_PACKAGE, args.gateway_mac_arg);
exit(EXIT_FAILURE);
}
zconf.gw_mac_set = 1;
}
if (args.source_mac_given) {
if (!parse_mac(zconf.hw_mac, args.source_mac_arg)) {
fprintf(stderr, "%s: invalid MAC address `%s'\n",
CMDLINE_PARSER_PACKAGE, args.gateway_mac_arg);
exit(EXIT_FAILURE);
}
log_debug("send",
"source MAC address specified on CLI: "
"%02x:%02x:%02x:%02x:%02x:%02x",
zconf.hw_mac[0], zconf.hw_mac[1], zconf.hw_mac[2],
zconf.hw_mac[3], zconf.hw_mac[4], zconf.hw_mac[5]);
zconf.hw_mac_set = 1;
}
// Check for a random seed
if (args.seed_given) {
zconf.seed = args.seed_arg;
zconf.seed_provided = 1;
} else {
// generate a seed randomly
if (!random_bytes(&zconf.seed, sizeof(uint64_t))) {
log_fatal("zmap", "unable to generate random bytes "
"needed for seed");
}
zconf.seed_provided = 0;
}
zconf.aes = aesrand_init_from_seed(zconf.seed);
// Set up sharding
zconf.shard_num = 0;
zconf.total_shards = 1;
if ((args.shard_given || args.shards_given) && !args.seed_given) {
log_fatal("zmap", "Need to specify seed if sharding a scan");
}
if (args.shard_given ^ args.shards_given) {
log_fatal(
"zmap",
"Need to specify both shard number and total number of shards");
}
if (args.shard_given) {
enforce_range("shard", args.shard_arg, 0, 65534);
}
if (args.shards_given) {
enforce_range("shards", args.shards_arg, 1, 65535);
}
SET_IF_GIVEN(zconf.shard_num, shard);
SET_IF_GIVEN(zconf.total_shards, shards);
if (zconf.shard_num >= zconf.total_shards) {
log_fatal("zmap",
"With %hhu total shards, shard number (%hhu)"
" must be in range [0, %hhu)",
zconf.total_shards, zconf.shard_num,
zconf.total_shards);
}
if (args.bandwidth_given) {
// Supported: G,g=*1000000000; M,m=*1000000 K,k=*1000 bits per
// second
zconf.bandwidth = atoi(args.bandwidth_arg);
char *suffix = args.bandwidth_arg;
while (*suffix >= '0' && *suffix <= '9') {
suffix++;
}
if (*suffix) {
switch (*suffix) {
case 'G':
case 'g':
zconf.bandwidth *= 1000000000;
break;
case 'M':
case 'm':
zconf.bandwidth *= 1000000;
break;
case 'K':
case 'k':
zconf.bandwidth *= 1000;
break;
default:
fprintf(stderr,
"%s: unknown bandwidth suffix '%s' "
"(supported suffixes are G, M and K)\n",
CMDLINE_PARSER_PACKAGE, suffix);
exit(EXIT_FAILURE);
}
}
}
if (args.batch_given && args.batch_arg >= 1 && args.batch_arg <= UINT8_MAX) {
zconf.batch = args.batch_arg;
} else if (args.batch_given) {
log_fatal("zmap", "batch size must be > 0 and <= 255");
}
if (args.max_targets_given) {
zconf.max_targets = parse_max_hosts(args.max_targets_arg);
}
// blocklist
if (blocklist_init(zconf.allowlist_filename, zconf.blocklist_filename,
zconf.destination_cidrs, zconf.destination_cidrs_len,
NULL, 0, zconf.ignore_invalid_hosts)) {
log_fatal("zmap", "unable to initialize blocklist / allowlist");
}
// if there's a list of ips to scan, then initialize PBM and populate
// it based on the provided file
if (zconf.list_of_ips_filename) {
zsend.list_of_ips_pbm = pbm_init();
zconf.list_of_ips_count = pbm_load_from_file(
zsend.list_of_ips_pbm, zconf.list_of_ips_filename);
}
// compute number of targets
uint64_t allowed = blocklist_count_allowed();
zconf.total_allowed = allowed;
zconf.total_disallowed = blocklist_count_not_allowed();
assert(allowed <= (1LL << 32));
if (!zconf.total_allowed) {
log_fatal("zmap", "zero eligible addresses to scan");
}
if (zconf.list_of_ips_count > 0 &&
0xFFFFFFFFU / zconf.list_of_ips_count > 100000) {
log_warn(
"zmap",
"list of IPs is small compared to address space. Performance will suffer, consider using an allowlist instead");
}
if (zconf.max_targets) {
zsend.max_targets = zconf.max_targets;
}
#ifndef PFRING
// Set the correct number of threads, default to min(4, number of cores on host - 1, as available)
if (args.sender_threads_given) {
zconf.senders = args.sender_threads_arg;
} else {
// use one fewer than the number of cores on the machine such that the
// receiver thread can use a core for processing responses
int available_cores = get_num_cores();
if (available_cores > 1) {
available_cores--;
}
int senders = min_int(available_cores, 4);
zconf.senders = senders;
log_debug("zmap", "will use %i sender threads based on core availability", senders);
}
if (2 * zconf.senders >= zsend.max_targets) {
log_warn(
"zmap",
"too few targets relative to senders, dropping to one sender");
zconf.senders = 1;
}
#else
zconf.senders = args.sender_threads_arg;
#endif
// Figure out what cores to bind to
if (args.cores_given) {
const char **core_list = NULL;
int len = 0;
split_string(args.cores_arg, &len, &core_list);
zconf.pin_cores_len = (uint32_t)len;
zconf.pin_cores =
xcalloc(zconf.pin_cores_len, sizeof(uint32_t));
for (uint32_t i = 0; i < zconf.pin_cores_len; ++i) {
zconf.pin_cores[i] = atoi(core_list[i]);
}
} else {
int num_cores = get_num_cores();
zconf.pin_cores_len = (uint32_t)num_cores;
zconf.pin_cores =
xcalloc(zconf.pin_cores_len, sizeof(uint32_t));
for (uint32_t i = 0; i < zconf.pin_cores_len; ++i) {
zconf.pin_cores[i] = i;
}
}
// PFRING
#ifdef PFRING
#define MAX_CARD_SLOTS 32768
#define QUEUE_LEN 8192
#define ZMAP_PF_BUFFER_SIZE 1536
#define ZMAP_PF_ZC_CLUSTER_ID 9627
uint32_t user_buffers = zconf.senders * 256;
uint32_t queue_buffers = zconf.senders * QUEUE_LEN;
uint32_t card_buffers = 2 * MAX_CARD_SLOTS;
uint32_t total_buffers =
user_buffers + queue_buffers + card_buffers + 2;
uint32_t metadata_len = 0;
uint32_t numa_node = 0; // TODO
zconf.pf.cluster = pfring_zc_create_cluster(
ZMAP_PF_ZC_CLUSTER_ID, ZMAP_PF_BUFFER_SIZE, metadata_len,
total_buffers, numa_node, NULL, NULL);
if (zconf.pf.cluster == NULL) {
log_fatal("zmap", "Could not create zc cluster: %s",
strerror(errno));
}
zconf.pf.buffers = xcalloc(user_buffers, sizeof(pfring_zc_pkt_buff *));
for (uint32_t i = 0; i < user_buffers; ++i) {
zconf.pf.buffers[i] =
pfring_zc_get_packet_handle(zconf.pf.cluster);
if (zconf.pf.buffers[i] == NULL) {
log_fatal("zmap", "Could not get ZC packet handle");
}
}
zconf.pf.send =
pfring_zc_open_device(zconf.pf.cluster, zconf.iface, tx_only, 0);
if (zconf.pf.send == NULL) {
log_fatal("zmap", "Could not open device %s for TX. [%s]",
zconf.iface, strerror(errno));
}
zconf.pf.recv =
pfring_zc_open_device(zconf.pf.cluster, zconf.iface, rx_only, 0);
if (zconf.pf.recv == NULL) {
log_fatal("zmap", "Could not open device %s for RX. [%s]",
zconf.iface, strerror(errno));
}
zconf.pf.queues = xcalloc(zconf.senders, sizeof(pfring_zc_queue *));
for (uint32_t i = 0; i < zconf.senders; ++i) {
zconf.pf.queues[i] =
pfring_zc_create_queue(zconf.pf.cluster, QUEUE_LEN);
if (zconf.pf.queues[i] == NULL) {
log_fatal("zmap", "Could not create queue: %s",
strerror(errno));
}
}
zconf.pf.prefetches = pfring_zc_create_buffer_pool(zconf.pf.cluster, 8);
if (zconf.pf.prefetches == NULL) {
log_fatal("zmap", "Could not open prefetch pool: %s",
strerror(errno));
}
#endif
// resume scan if requested
start_zmap();
fclose(log_location);
cmdline_parser_free(&args);
free(params);
return EXIT_SUCCESS;
}