Skip to content

Commit b7c815a

Browse files
Fix for zmap#913 and added IPv4 scan coverage integration test and python wrapper with --fast-dryrun (zmap#916)
* enable monitor thread and don't print packets on dryrun, for testing * missed an int32 * Revert "enable monitor thread and don't print packets on dryrun, for testing" This reverts commit c8bb512. * Revert "Revert "enable monitor thread and don't print packets on dryrun, for testing"" This reverts commit 2976166. * use monitor thread with dryrun but not receive thread * print out packets to std out and also print status updates * add fast dryrun option * added fast-dryrun for increased performance, printing only dst IP and dst Port * added a potentially working test, though it is FAR too slow to be usable for full IPv4 scans * faster test, 2.5 Mpps * unit tested verify fn * working @ 2.56 Mpps * speed up writing fast-dryrun * better error handling * eek'd out 25% more perf. * added details on reproducing issue * remove debugging logs and clean up * off-by one error, candidate should be inclusive * added a few more tests, added Dockerfile, and gh actions yml * update manpages * correct desc. of max_candidate * cleanup * added more integration tests for small subnets * added test comments and cleaned up * cleanup and make it more clear that we have a max_target and max_ip * set top level dir correctly * add dependency for bitarray * handled starting monitor/recv threads in different CLI args combos
1 parent d3ef219 commit b7c815a

19 files changed

+473
-35
lines changed

.github/workflows/pytest.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM ubuntu:latest
22

33
RUN apt-get update
44
RUN apt-get install -y build-essential cmake libgmp3-dev gengetopt libpcap-dev flex \
5-
byacc libjson-c-dev pkg-config libunistring-dev libjudy-dev cmake make python3 python3-pytest python3-timeout-decorator curl
5+
byacc libjson-c-dev pkg-config libunistring-dev libjudy-dev cmake make python3 python3-pytest python3-timeout-decorator python3-bitarray curl
66
RUN apt-get clean
77

88

.github/workflows/scan_coverage.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Daily ZMap Coverage Test
2+
# This workflow is triggered daily against main
3+
# It tests that in --fast-dryrun mode that ZMap scans all expected IPs+ports and doesn't scan targets multiple times
4+
# This test takes a while to run
5+
6+
on:
7+
# Allow manual triggering via the GitHub Actions UI
8+
workflow_dispatch:
9+
10+
# Schedule the workflow to run once per day
11+
schedule:
12+
- cron: "0 0 * * *" # Adjust the time as needed (this example runs at midnight UTC)
13+
14+
15+
env:
16+
ENABLE_DEVELOPMENT: ON
17+
ENABLE_LOG_TRACE: ON
18+
WITH_AES_HW: ON
19+
20+
jobs:
21+
example_job:
22+
runs-on: ubuntu-latest
23+
24+
steps:
25+
- name: Checkout repository
26+
uses: actions/checkout@v3
27+
28+
- name: Run Scan Coverage Tests
29+
run: |
30+
docker build -t coverage-container -f .github/workflows/scan_coverage_pytest.Dockerfile .
31+
docker run coverage-container
32+
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
FROM arm64v8/ubuntu:latest
2+
3+
RUN apt-get update
4+
RUN apt-get install -y build-essential cmake libgmp3-dev gengetopt libpcap-dev flex byacc libjson-c-dev pkg-config \
5+
libunistring-dev libjudy-dev cmake make python3 python3-pytest python3-timeout-decorator python3-bitarray curl
6+
RUN apt-get clean
7+
8+
9+
10+
WORKDIR /zmap
11+
COPY . .
12+
RUN rm -f CMakeCache.txt # if building locally, this file can be present and cause build failure
13+
RUN cmake -DENABLE_DEVELOPMENT=$ENABLE_DEVELOPMENT -DENABLE_LOG_TRACE=$ENABLE_LOG_TRACE -DWITH_AES_HW=$WITH_AES_HW . \
14+
&& make -j4
15+
16+
WORKDIR /zmap/test/integration-tests
17+
# need to get the gateway mac
18+
RUN curl zmap.io \
19+
# launch the test
20+
&& python3 test_full_ipv4_multi_port_scan.py

src/cyclic.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ static cyclic_group_t groups[] = {
5858
{// 2^8 + 1
5959
.prime = 257,
6060
.known_primroot = 3,
61-
.prime_factors = {2},
61+
.prime_factors = {2}, // prime factors of prime - 1
6262
.num_prime_factors = 1},
6363
{// 2^16 + 1
6464
.prime = 65537,

src/iterator.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,17 @@ iterator_t *iterator_init(uint8_t num_threads, uint16_t shard,
8383
iterator_t *it = xmalloc(sizeof(struct iterator));
8484
const cyclic_group_t *group = get_group(group_min_size);
8585
if (num_addrs > (1LL << 32)) {
86-
zsend.max_index = 0xFFFFFFFF;
86+
zsend.max_ip_index = 0xFFFFFFFF;
8787
} else {
88-
zsend.max_index = (uint32_t)num_addrs;
88+
zsend.max_ip_index = (uint32_t)num_addrs;
8989
}
90-
log_debug("iterator", "max index %u", zsend.max_index);
90+
log_debug("iterator", "max ip index %ul", zsend.max_ip_index);
91+
// The candidate is upper-bounded by the modulus of the group, which is the prime number chosen in cyclic.c.
92+
// All primes are chosen to be greater than the number of allowed targets.
93+
// We must re-roll if the candidate target is out of bounds of (2 ** 32) * (2 ** number of ports).
94+
zsend.max_target_index = 1ULL << (32 + bits_for_port);
95+
log_debug("iterator", "max target index %ull", zsend.max_target_index);
96+
9197
it->cycle = make_cycle(group, zconf.aes);
9298
it->num_threads = num_threads;
9399
it->curr_threads = num_threads;

src/monitor.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,11 @@ double compute_remaining_time(double age, uint64_t packets_sent,
152152
(double)zrecv.filter_success / zconf.max_results;
153153
remaining[3] = (1. - done) * (age / done);
154154
}
155-
if (zsend.max_index) {
155+
if (zsend.max_ip_index) {
156156
double done =
157157
(double)packets_sent /
158-
((uint64_t)zsend.max_index * zconf.ports->port_count * zconf.packet_streams /
159-
zconf.total_shards);
158+
((uint64_t)zsend.max_ip_index * zconf.ports->port_count * zconf.packet_streams /
159+
zconf.total_shards);
160160
remaining[4] =
161161
(1. - done) * (age / done) + zconf.cooldown_secs;
162162
}

src/probe_modules/module_tcp_synscan.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ void synscan_print_packet(FILE *fp, void *packet)
137137
struct ether_header *ethh = (struct ether_header *)packet;
138138
struct ip *iph = (struct ip *)&ethh[1];
139139
struct tcphdr *tcph = (struct tcphdr *)&iph[1];
140+
if (zconf.fast_dryrun) {
141+
// We'll just print a binary representation of the dst IP and the dst Port to reduce data output/save time
142+
struct in_addr *dest_IP = (struct in_addr *)&(iph->ip_dst);
143+
// Writing binary IP addresses
144+
const uint8_t IP_ADDR_LEN = 4;
145+
const uint8_t TCP_PORT_LEN = 2;
146+
fwrite(&(dest_IP->s_addr), IP_ADDR_LEN,1, fp); // Write destination IP (binary)
147+
fwrite(&(tcph->th_dport), TCP_PORT_LEN,1, fp); // Write destination port (binary)
148+
return;
149+
}
140150
fprintf(fp,
141151
"tcp { source: %u | dest: %u | seq: %u | checksum: %#04X }\n",
142152
ntohs(tcph->th_sport), ntohs(tcph->th_dport),

src/send.c

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ int send_run(sock_t st, shard_t *s)
276276
20;
277277
last_time = steady_now();
278278
assert(interval > 0);
279-
assert(delay > 0);
279+
if (delay == 0) {
280+
// at extremely high bandwidths, the delay could be set to zero.
281+
// this breaks the multiplier logic below, so we'll hard-set it to 1 in this case.
282+
delay = 1;
283+
}
280284
}
281285
}
282286
int attempts = zconf.retries + 1;
@@ -413,10 +417,17 @@ int send_run(sock_t st, shard_t *s)
413417
batch->packets[batch->len].len = (uint32_t)length;
414418

415419
if (zconf.dryrun) {
416-
lock_file(stdout);
417-
zconf.probe_module->print_packet(stdout,
418-
batch->packets[batch->len].buf);
419-
unlock_file(stdout);
420+
batch->len++;
421+
if (batch->len == batch->capacity) {
422+
lock_file(stdout);
423+
for (int i = 0; i < batch->len; i++) {
424+
zconf.probe_module->print_packet(stdout,
425+
batch->packets[i].buf);
426+
}
427+
unlock_file(stdout);
428+
// reset batch length for next batch
429+
batch->len = 0;
430+
}
420431
} else {
421432
batch->len++;
422433
if (batch->len == batch->capacity) {
@@ -465,6 +476,15 @@ int send_run(sock_t st, shard_t *s)
465476
cleanup:
466477
if (!zconf.dryrun && send_batch(st, batch, attempts) < 0) {
467478
log_error("send_batch cleanup", "could not send remaining batch packets: %s", strerror(errno));
479+
} else if (zconf.dryrun) {
480+
lock_file(stdout);
481+
for (int i = 0; i < batch->len; i++) {
482+
zconf.probe_module->print_packet(stdout,
483+
batch->packets[i].buf);
484+
}
485+
unlock_file(stdout);
486+
// reset batch length for next batch
487+
batch->len = 0;
468488
}
469489
free_packet_batch(batch);
470490
s->cb(s->thread_id, s->arg);

src/shard.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ static void shard_roll_to_valid(shard_t *s)
3232
{
3333
uint64_t current_ip_index = (s->current - 1) >> s->bits_for_port;
3434
uint16_t candidate_port = extract_port(s->current - 1, s->bits_for_port);
35-
if (current_ip_index < zsend.max_index && candidate_port < zconf.ports->port_count) {
35+
if (current_ip_index < zsend.max_ip_index && candidate_port < zconf.ports->port_count) {
3636
return;
3737
}
3838
shard_get_next_target(s);
@@ -105,7 +105,6 @@ void shard_init(shard_t *shard, uint16_t shard_idx, uint16_t num_shards,
105105
shard->params.last = (uint64_t)mpz_get_ui(stop_m);
106106
shard->params.factor = cycle->generator;
107107
shard->params.modulus = cycle->group->prime;
108-
//
109108
shard->bits_for_port = bits_for_port;
110109

111110
// Set the shard at the beginning.
@@ -159,7 +158,7 @@ static inline uint64_t shard_get_next_elem(shard_t *shard)
159158
{
160159
shard->current *= shard->params.factor;
161160
shard->current %= shard->params.modulus;
162-
return (uint64_t)shard->current;
161+
return shard->current;
163162
}
164163

165164
target_t shard_get_next_target(shard_t *shard)
@@ -176,11 +175,17 @@ target_t shard_get_next_target(shard_t *shard)
176175
return (target_t){
177176
.ip = 0, .port = 0, .status = ZMAP_SHARD_DONE};
178177
}
178+
if (candidate >= zsend.max_target_index) {
179+
// If the candidate is out of bounds, re-roll. This will happen since we choose primes/moduli that are
180+
// larger than the number of allowed targets. The IP is bounded below by checking against zsend.max_ip_index.
181+
continue;
182+
}
183+
// Good candidate, proceed with it.
179184
uint32_t candidate_ip =
180185
extract_ip(candidate - 1, shard->bits_for_port);
181186
uint16_t candidate_port =
182187
extract_port(candidate - 1, shard->bits_for_port);
183-
if (candidate_ip < zsend.max_index &&
188+
if (candidate_ip < zsend.max_ip_index &&
184189
candidate_port < zconf.ports->port_count) {
185190
shard->iterations++;
186191
return (target_t){

src/state.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ struct state_conf zconf = {
2424
.dedup_method = 0,
2525
.dedup_window_size = 0,
2626
.dryrun = 0,
27+
.fast_dryrun = 0,
2728
.hw_mac = {0},
2829
.hw_mac_set = 0,
2930
.gw_ip = 0,

0 commit comments

Comments
 (0)