1 /* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
7
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <https://www.gnu.org/licenses/>.
15 */
16
17 #include <assert.h>
18 #include <errno.h>
19 #include <getopt.h>
20 #include <ifaddrs.h>
21 #include <inttypes.h>
22 #include <poll.h>
23 #include <pthread.h>
24 #include <signal.h>
25 #include <stdbool.h>
26 #include <stdint.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include <arpa/inet.h>
34 #include <netinet/in.h>
35 #include <net/if.h>
36 #include <sys/ioctl.h>
37 #include <sys/socket.h>
38 #include <sys/resource.h>
39
40 #include "libknot/libknot.h"
41 #include "libknot/xdp.h"
42 #include "contrib/macros.h"
43 #include "contrib/mempattern.h"
44 #include "contrib/openbsd/strlcat.h"
45 #include "contrib/openbsd/strlcpy.h"
46 #include "contrib/os.h"
47 #include "contrib/sockaddr.h"
48 #include "contrib/ucw/mempool.h"
49 #include "utils/common/params.h"
50 #include "utils/kxdpgun/ip_route.h"
51 #include "utils/kxdpgun/load_queries.h"
52
53 #define PROGRAM_NAME "kxdpgun"
54 #define SPACE " "
55
56 enum {
57 KXDPGUN_WAIT,
58 KXDPGUN_START,
59 KXDPGUN_STOP,
60 };
61
62 volatile int xdp_trigger = KXDPGUN_WAIT;
63
64 volatile unsigned stats_trigger = 0;
65
66 unsigned global_cpu_aff_start = 0;
67 unsigned global_cpu_aff_step = 1;
68
69 #define LOCAL_PORT_DEFAULT 53
70 #define LOCAL_PORT_MIN 2000
71 #define LOCAL_PORT_MAX 65535
72
73 #define RCODE_MAX (0x0F + 1)
74
75 typedef struct {
76 size_t collected;
77 uint64_t duration;
78 uint64_t qry_sent;
79 uint64_t synack_recv;
80 uint64_t ans_recv;
81 uint64_t finack_recv;
82 uint64_t rst_recv;
83 uint64_t size_recv;
84 uint64_t wire_recv;
85 uint64_t rcodes_recv[RCODE_MAX];
86 pthread_mutex_t mutex;
87 } kxdpgun_stats_t;
88
89 static kxdpgun_stats_t global_stats = { 0 };
90
91 typedef struct {
92 char dev[IFNAMSIZ];
93 uint64_t qps, duration;
94 unsigned at_once;
95 uint16_t msgid;
96 uint16_t edns_size;
97 uint8_t local_mac[6], target_mac[6];
98 struct sockaddr_storage local_ip, target_ip;
99 uint8_t local_ip_range;
100 bool ipv6;
101 bool tcp;
102 uint16_t target_port;
103 uint32_t listen_port; // KNOT_XDP_LISTEN_PORT_*
104 unsigned n_threads, thread_id;
105 } xdp_gun_ctx_t;
106
107 const static xdp_gun_ctx_t ctx_defaults = {
108 .dev[0] = '\0',
109 .edns_size = 1232,
110 .qps = 1000,
111 .duration = 5000000UL, // usecs
112 .at_once = 10,
113 .target_port = LOCAL_PORT_DEFAULT,
114 .listen_port = KNOT_XDP_LISTEN_PORT_PASS | LOCAL_PORT_MIN,
115 };
116
sigterm_handler(int signo)117 static void sigterm_handler(int signo)
118 {
119 assert(signo == SIGTERM || signo == SIGINT);
120 xdp_trigger = KXDPGUN_STOP;
121 }
122
sigusr_handler(int signo)123 static void sigusr_handler(int signo)
124 {
125 assert(signo == SIGUSR1);
126 if (global_stats.collected == 0) {
127 stats_trigger++;
128 }
129 }
130
clear_stats(kxdpgun_stats_t * st)131 static void clear_stats(kxdpgun_stats_t *st)
132 {
133 pthread_mutex_lock(&st->mutex);
134 st->duration = 0;
135 st->qry_sent = 0;
136 st->synack_recv = 0;
137 st->ans_recv = 0;
138 st->finack_recv = 0;
139 st->rst_recv = 0;
140 st->size_recv = 0;
141 st->wire_recv = 0;
142 st->collected = 0;
143 memset(st->rcodes_recv, 0, sizeof(st->rcodes_recv));
144 pthread_mutex_unlock(&st->mutex);
145 }
146
collect_stats(kxdpgun_stats_t * into,const kxdpgun_stats_t * what)147 static size_t collect_stats(kxdpgun_stats_t *into, const kxdpgun_stats_t *what)
148 {
149 pthread_mutex_lock(&into->mutex);
150 into->duration = MAX(into->duration, what->duration);
151 into->qry_sent += what->qry_sent;
152 into->synack_recv += what->synack_recv;
153 into->ans_recv += what->ans_recv;
154 into->finack_recv += what->finack_recv;
155 into->rst_recv += what->rst_recv;
156 into->size_recv += what->size_recv;
157 into->wire_recv += what->wire_recv;
158 for (int i = 0; i < RCODE_MAX; i++) {
159 into->rcodes_recv[i] += what->rcodes_recv[i];
160 }
161 size_t res = ++into->collected;
162 pthread_mutex_unlock(&into->mutex);
163 return res;
164 }
165
print_stats(kxdpgun_stats_t * st,bool tcp,bool recv)166 static void print_stats(kxdpgun_stats_t *st, bool tcp, bool recv)
167 {
168 pthread_mutex_lock(&st->mutex);
169
170 #define ps(counter) ((counter) * 1000 / (st->duration / 1000))
171 #define pct(counter) ((counter) * 100 / st->qry_sent)
172
173 printf("total %s %"PRIu64" (%"PRIu64" pps)\n",
174 tcp ? "SYN: " : "queries:", st->qry_sent, ps(st->qry_sent));
175 if (st->qry_sent > 0 && recv) {
176 if (tcp) {
177 printf("total established: %"PRIu64" (%"PRIu64" pps) (%"PRIu64"%%)\n",
178 st->synack_recv, ps(st->synack_recv), pct(st->synack_recv));
179 }
180 printf("total replies: %"PRIu64" (%"PRIu64" pps) (%"PRIu64"%%)\n",
181 st->ans_recv, ps(st->ans_recv), pct(st->ans_recv));
182 if (tcp) {
183 printf("total closed: %"PRIu64" (%"PRIu64" pps) (%"PRIu64"%%)\n",
184 st->finack_recv, ps(st->finack_recv), pct(st->finack_recv));
185 printf("total reset: %"PRIu64" (%"PRIu64" pps) (%"PRIu64"%%)\n",
186 st->rst_recv, ps(st->rst_recv), pct(st->rst_recv));
187 }
188 printf("average DNS reply size: %"PRIu64" B\n",
189 st->ans_recv > 0 ? st->size_recv / st->ans_recv : 0);
190 printf("average Ethernet reply rate: %"PRIu64" bps (%.2f Mbps)\n",
191 ps(st->wire_recv * 8), ps((float)st->wire_recv * 8 / (1000 * 1000)));
192
193 for (int i = 0; i < RCODE_MAX; i++) {
194 if (st->rcodes_recv[i] > 0) {
195 const knot_lookup_t *rcode = knot_lookup_by_id(knot_rcode_names, i);
196 const char *rcname = rcode == NULL ? "unknown" : rcode->name;
197 int space = MAX(9 - strlen(rcname), 0);
198 printf("responded %s: %.*s%"PRIu64"\n",
199 rcname, space, " ", st->rcodes_recv[i]);
200 }
201 }
202 }
203 printf("duration: %"PRIu64" s\n", (st->duration / (1000 * 1000)));
204
205 pthread_mutex_unlock(&st->mutex);
206 }
207
timer_start(struct timespec * timesp)208 inline static void timer_start(struct timespec *timesp)
209 {
210 clock_gettime(CLOCK_MONOTONIC, timesp);
211 }
212
timer_end(struct timespec * timesp)213 inline static uint64_t timer_end(struct timespec *timesp)
214 {
215 struct timespec end;
216 clock_gettime(CLOCK_MONOTONIC, &end);
217 uint64_t res = (end.tv_sec - timesp->tv_sec) * (uint64_t)1000000;
218 res += ((int64_t)end.tv_nsec - timesp->tv_nsec) / 1000;
219 return res;
220 }
221
addr_bits(bool ipv6)222 static unsigned addr_bits(bool ipv6)
223 {
224 return ipv6 ? 128 : 32;
225 }
226
shuffle_sockaddr4(void * dst_v,struct sockaddr_storage * src_ss,uint64_t increment)227 static void shuffle_sockaddr4(void *dst_v, struct sockaddr_storage *src_ss, uint64_t increment)
228 {
229 struct sockaddr_in *dst = dst_v, *src = (struct sockaddr_in *)src_ss;
230 memcpy(&dst->sin_addr, &src->sin_addr, sizeof(dst->sin_addr));
231 if (increment > 0) {
232 dst->sin_addr.s_addr = htobe32(be32toh(src->sin_addr.s_addr) + increment);
233 }
234 }
235
shuffle_sockaddr6(void * dst_v,struct sockaddr_storage * src_ss,uint64_t increment)236 static void shuffle_sockaddr6(void *dst_v, struct sockaddr_storage *src_ss, uint64_t increment)
237 {
238 struct sockaddr_in6 *dst = dst_v, *src = (struct sockaddr_in6 *)src_ss;
239 memcpy(&dst->sin6_addr, &src->sin6_addr, sizeof(dst->sin6_addr));
240 if (increment > 0) {
241 dst->sin6_addr.__in6_u.__u6_addr32[2] =
242 htobe32(be32toh(src->sin6_addr.__in6_u.__u6_addr32[2]) + (increment >> 32));
243 dst->sin6_addr.__in6_u.__u6_addr32[3] =
244 htobe32(be32toh(src->sin6_addr.__in6_u.__u6_addr32[3]) + (increment & 0xffffffff));
245 }
246 }
247
shuffle_sockaddr(struct sockaddr_in6 * dst,struct sockaddr_storage * ss,uint16_t port,uint64_t increment)248 static void shuffle_sockaddr(struct sockaddr_in6 *dst, struct sockaddr_storage *ss,
249 uint16_t port, uint64_t increment)
250 {
251 dst->sin6_family = ss->ss_family;
252 dst->sin6_port = htobe16(port);
253 if (ss->ss_family == AF_INET6) {
254 shuffle_sockaddr6(dst, ss, increment);
255 } else {
256 shuffle_sockaddr4(dst, ss, increment);
257 }
258 }
259
next_payload(struct pkt_payload ** payload,int increment)260 static void next_payload(struct pkt_payload **payload, int increment)
261 {
262 if (*payload == NULL) {
263 *payload = global_payloads;
264 }
265 for (int i = 0; i < increment; i++) {
266 if ((*payload)->next == NULL) {
267 *payload = global_payloads;
268 } else {
269 *payload = (*payload)->next;
270 }
271 }
272 }
273
put_dns_payload(struct iovec * put_into,bool zero_copy,xdp_gun_ctx_t * ctx,struct pkt_payload ** payl)274 static void put_dns_payload(struct iovec *put_into, bool zero_copy, xdp_gun_ctx_t *ctx, struct pkt_payload **payl)
275 {
276 if (zero_copy) {
277 put_into->iov_base = (*payl)->payload;
278 } else {
279 memcpy(put_into->iov_base, (*payl)->payload, (*payl)->len);
280 }
281 put_into->iov_len = (*payl)->len;
282 next_payload(payl, ctx->n_threads);
283 }
284
alloc_pkts(knot_xdp_msg_t * pkts,struct knot_xdp_socket * xsk,xdp_gun_ctx_t * ctx,uint64_t tick)285 static int alloc_pkts(knot_xdp_msg_t *pkts, struct knot_xdp_socket *xsk,
286 xdp_gun_ctx_t *ctx, uint64_t tick)
287 {
288 uint64_t unique = (tick * ctx->n_threads + ctx->thread_id) * ctx->at_once;
289
290 knot_xdp_msg_flag_t flags = ctx->ipv6 ? KNOT_XDP_MSG_IPV6 : 0;
291 if (ctx->tcp) {
292 flags |= (KNOT_XDP_MSG_TCP | KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_MSS);
293 }
294
295 for (int i = 0; i < ctx->at_once; i++) {
296 int ret = knot_xdp_send_alloc(xsk, flags, &pkts[i]);
297 if (ret != KNOT_EOK) {
298 return i;
299 }
300
301 uint16_t local_port = LOCAL_PORT_MIN + unique % (LOCAL_PORT_MAX + 1 - LOCAL_PORT_MIN);
302 uint64_t ip_incr = unique % (1 << (addr_bits(ctx->ipv6) - ctx->local_ip_range));
303 shuffle_sockaddr(&pkts[i].ip_from, &ctx->local_ip, local_port, ip_incr);
304 shuffle_sockaddr(&pkts[i].ip_to, &ctx->target_ip, ctx->target_port, 0);
305
306 memcpy(pkts[i].eth_from, ctx->local_mac, 6);
307 memcpy(pkts[i].eth_to, ctx->target_mac, 6);
308
309 unique++;
310 }
311 return ctx->at_once;
312 }
313
check_dns_payload(struct iovec * payl,xdp_gun_ctx_t * ctx,kxdpgun_stats_t * st)314 inline static bool check_dns_payload(struct iovec *payl, xdp_gun_ctx_t *ctx,
315 kxdpgun_stats_t *st)
316 {
317 if (payl->iov_len < KNOT_WIRE_HEADER_SIZE ||
318 memcmp(payl->iov_base, &ctx->msgid, sizeof(ctx->msgid)) != 0) {
319 return false;
320 }
321 st->rcodes_recv[((uint8_t *)payl->iov_base)[3] & 0x0F]++;
322 st->size_recv += payl->iov_len;
323 st->ans_recv++;
324 return true;
325 }
326
xdp_gun_thread(void * _ctx)327 void *xdp_gun_thread(void *_ctx)
328 {
329 xdp_gun_ctx_t *ctx = _ctx;
330 struct knot_xdp_socket *xsk;
331 struct timespec timer;
332 knot_xdp_msg_t pkts[ctx->at_once];
333 uint64_t errors = 0, lost = 0, duration = 0;
334 kxdpgun_stats_t local_stats = { 0 };
335 unsigned stats_triggered = 0;
336 knot_tcp_table_t *tcp_table = NULL;
337
338 if (ctx->tcp) {
339 tcp_table = knot_tcp_table_new(ctx->qps);
340 if (tcp_table == NULL) {
341 printf("failed to allocate TCP connection table\n");
342 return NULL;
343 }
344 }
345
346 knot_xdp_load_bpf_t mode = (ctx->thread_id == 0 ?
347 KNOT_XDP_LOAD_BPF_ALWAYS : KNOT_XDP_LOAD_BPF_NEVER);
348 int ret = knot_xdp_init(&xsk, ctx->dev, ctx->thread_id, ctx->listen_port, mode);
349 if (ret != KNOT_EOK) {
350 printf("failed to initialize XDP socket#%u: %s\n",
351 ctx->thread_id, knot_strerror(ret));
352 knot_tcp_table_free(tcp_table);
353 return NULL;
354 }
355
356 struct pollfd pfd = { knot_xdp_socket_fd(xsk), POLLIN, 0 };
357
358 while (xdp_trigger == KXDPGUN_WAIT) {
359 usleep(1000);
360 }
361
362 uint64_t tick = 0;
363 struct pkt_payload *payload_ptr = NULL;
364 next_payload(&payload_ptr, ctx->thread_id);
365
366 timer_start(&timer);
367
368 while (duration < ctx->duration + 1000000) {
369
370 // sending part
371 if (duration < ctx->duration) {
372 while (1) {
373 knot_xdp_send_prepare(xsk);
374 int alloced = alloc_pkts(pkts, xsk, ctx, tick);
375 if (alloced < ctx->at_once) {
376 lost++;
377 if (alloced == 0) {
378 break;
379 }
380 }
381
382 if (ctx->tcp) {
383 for (int i = 0; i < alloced; i++) {
384 pkts[i].payload.iov_len = 0;
385 }
386 } else {
387 for (int i = 0; i < alloced; i++) {
388 put_dns_payload(&pkts[i].payload, false,
389 ctx, &payload_ptr);
390 }
391 }
392
393 uint32_t really_sent = 0;
394 (void)knot_xdp_send(xsk, pkts, alloced, &really_sent);
395 assert(really_sent == alloced);
396 local_stats.qry_sent += really_sent;
397 (void)knot_xdp_send_finish(xsk);
398
399 break;
400 }
401 }
402
403 // receiving part
404 if (!(ctx->listen_port & KNOT_XDP_LISTEN_PORT_DROP)) {
405 while (1) {
406 ret = poll(&pfd, 1, 0);
407 if (ret < 0) {
408 errors++;
409 break;
410 }
411 if (!pfd.revents) {
412 break;
413 }
414
415 uint32_t recvd = 0;
416 size_t wire = 0;
417 (void)knot_xdp_recv(xsk, pkts, ctx->at_once, &recvd, &wire);
418 if (recvd == 0) {
419 break;
420 }
421 if (ctx->tcp) {
422 uint32_t ack_errors = 0;
423 knot_tcp_relay_dynarray_t relays = { 0 };
424 ret = knot_tcp_relay(xsk, pkts, recvd, tcp_table, NULL,
425 &relays, &ack_errors);
426 lost += ack_errors;
427 if (ret != KNOT_EOK) {
428 errors++;
429 break;
430 }
431
432 size_t relays_answer = relays.size;
433 for (size_t i = 0; i < relays_answer; i++) {
434 knot_tcp_relay_t *rl = &knot_tcp_relay_dynarray_arr(&relays)[i];
435 struct iovec payl;
436 switch (rl->action) {
437 case XDP_TCP_ESTABLISH:
438 local_stats.synack_recv++;
439 rl->answer = XDP_TCP_ANSWER | XDP_TCP_DATA;
440 put_dns_payload(&payl, true, ctx, &payload_ptr);
441 ret = knot_tcp_relay_answer(&relays, rl, payl.iov_base,
442 payl.iov_len);
443 if (ret != KNOT_EOK) {
444 errors++;
445 }
446 break;
447 case XDP_TCP_DATA:
448 if (check_dns_payload(&rl->data, ctx, &local_stats)) {
449 rl->answer = XDP_TCP_ANSWER | XDP_TCP_CLOSE;
450 }
451 break;
452 case XDP_TCP_CLOSE:
453 local_stats.finack_recv++;
454 break;
455 case XDP_TCP_RESET:
456 local_stats.rst_recv++;
457 break;
458 default:
459 break;
460 }
461 }
462
463 ret = knot_tcp_send(xsk, knot_tcp_relay_dynarray_arr(&relays),
464 relays.size);
465 if (ret != KNOT_EOK) {
466 errors++;
467 }
468 (void)knot_xdp_send_finish(xsk);
469
470 knot_tcp_relay_free(&relays);
471 } else {
472 for (int i = 0; i < recvd; i++) {
473 (void)check_dns_payload(&pkts[i].payload, ctx,
474 &local_stats);
475 }
476 }
477 local_stats.wire_recv += wire;
478 knot_xdp_recv_finish(xsk, pkts, recvd);
479 pfd.revents = 0;
480 }
481 }
482
483 // speed and signal part
484 uint64_t dura_exp = (local_stats.qry_sent * 1000000) / ctx->qps;
485 duration = timer_end(&timer);
486 if (xdp_trigger == KXDPGUN_STOP && ctx->duration > duration) {
487 ctx->duration = duration;
488 }
489 if (stats_trigger > stats_triggered) {
490 assert(stats_trigger == stats_triggered + 1);
491 stats_triggered++;
492
493 local_stats.duration = duration;
494 size_t collected = collect_stats(&global_stats, &local_stats);
495 assert(collected <= ctx->n_threads);
496 if (collected == ctx->n_threads) {
497 print_stats(&global_stats, ctx->tcp,
498 !(ctx->listen_port & KNOT_XDP_LISTEN_PORT_DROP));
499 clear_stats(&global_stats);
500 }
501 }
502 if (dura_exp > duration) {
503 usleep(dura_exp - duration);
504 }
505 if (duration > ctx->duration) {
506 usleep(1000);
507 }
508 tick++;
509 }
510
511 knot_xdp_deinit(xsk);
512
513 knot_tcp_table_free(tcp_table);
514
515 char recv_str[40] = "", lost_str[40] = "", err_str[40] = "";
516 if (!(ctx->listen_port & KNOT_XDP_LISTEN_PORT_DROP)) {
517 (void)snprintf(recv_str, sizeof(recv_str), ", received %"PRIu64, local_stats.ans_recv);
518 }
519 if (lost > 0) {
520 (void)snprintf(lost_str, sizeof(lost_str), ", lost %"PRIu64, lost);
521 }
522 if (errors > 0) {
523 (void)snprintf(err_str, sizeof(err_str), ", errors %"PRIu64, errors);
524 }
525 printf("thread#%02u: sent %"PRIu64"%s%s%s\n",
526 ctx->thread_id, local_stats.qry_sent, recv_str, lost_str, err_str);
527 local_stats.duration = ctx->duration;
528 collect_stats(&global_stats, &local_stats);
529
530 return NULL;
531 }
532
dev2mac(const char * dev,uint8_t * mac)533 static int dev2mac(const char *dev, uint8_t *mac)
534 {
535 struct ifreq ifr;
536 memset(&ifr, 0, sizeof(ifr));
537 int fd = socket(AF_INET, SOCK_DGRAM, 0);
538 if (fd < 0) {
539 return -errno;
540 }
541 strlcpy(ifr.ifr_name, dev, IFNAMSIZ);
542
543 int ret = ioctl(fd, SIOCGIFHWADDR, &ifr);
544 if (ret >= 0) {
545 memcpy(mac, ifr.ifr_hwaddr.sa_data, 6);
546 } else {
547 ret = -errno;
548 }
549 close(fd);
550 return ret;
551 }
552
configure_target(char * target_str,char * local_ip,xdp_gun_ctx_t * ctx)553 static bool configure_target(char *target_str, char *local_ip, xdp_gun_ctx_t *ctx)
554 {
555 int val;
556 char *at = strrchr(target_str, '@');
557 if (at != NULL && (val = atoi(at + 1)) > 0 && val <= 0xffff) {
558 ctx->target_port = val;
559 *at = '\0';
560 }
561
562 ctx->ipv6 = false;
563 if (!inet_aton(target_str, &((struct sockaddr_in *)&ctx->target_ip)->sin_addr)) {
564 ctx->ipv6 = true;
565 ctx->target_ip.ss_family = AF_INET6;
566 if (inet_pton(AF_INET6, target_str, &((struct sockaddr_in6 *)&ctx->target_ip)->sin6_addr) <= 0) {
567 printf("invalid target IP\n");
568 return false;
569 }
570 } else {
571 ctx->target_ip.ss_family = AF_INET;
572 }
573
574 struct sockaddr_storage via = { 0 };
575 int ret = ip_route_get(&ctx->target_ip, &via, &ctx->local_ip, ctx->dev);
576 if (ret < 0) {
577 printf("can't find route to `%s`: %s\n", target_str, strerror(-ret));
578 return false;
579 }
580
581 ctx->local_ip_range = addr_bits(ctx->ipv6); // by default use one IP
582 if (local_ip != NULL) {
583 at = strrchr(local_ip, '/');
584 if (at != NULL && (val = atoi(at + 1)) > 0 && val <= ctx->local_ip_range) {
585 ctx->local_ip_range = val;
586 *at = '\0';
587 }
588 if (ctx->ipv6) {
589 if (ctx->local_ip_range < 64 ||
590 inet_pton(AF_INET6, local_ip, &((struct sockaddr_in6 *)&ctx->local_ip)->sin6_addr) <= 0) {
591 printf("invalid local IPv6 or unsupported prefix length\n");
592 return false;
593 }
594 } else {
595 if (inet_pton(AF_INET, local_ip, &((struct sockaddr_in *)&ctx->local_ip)->sin_addr) <= 0) {
596 printf("invalid local IPv4\n");
597 return false;
598 }
599 }
600 }
601
602 const struct sockaddr_storage *neigh = via.ss_family == AF_UNSPEC ? &ctx->target_ip : &via;
603 ret = ip_neigh_get(neigh, true, ctx->target_mac);
604 if (ret < 0) {
605 char neigh_str[256] = { 0 };
606 (void)sockaddr_tostr(neigh_str, sizeof(neigh_str), neigh);
607 printf("failed to get remote MAC of target/gateway `%s`: %s\n", neigh_str, strerror(-ret));
608 return false;
609 }
610
611 ret = dev2mac(ctx->dev, ctx->local_mac);
612 if (ret < 0) {
613 printf("failed to get MAC of device `%s`: %s\n", ctx->dev, strerror(-ret));
614 return false;
615 }
616
617 ret = knot_eth_queues(ctx->dev);
618 if (ret >= 0) {
619 ctx->n_threads = ret;
620 } else {
621 printf("unable to get number of queues for %s: %s\n", ctx->dev,
622 knot_strerror(ret));
623 return false;
624 }
625
626 return true;
627 }
628
print_help(void)629 static void print_help(void)
630 {
631 printf("Usage: %s [parameters] -i <queries_file> <dest_ip>\n"
632 "\n"
633 "Parameters:\n"
634 " -t, --duration <sec> "SPACE"Duration of traffic generation.\n"
635 " "SPACE" (default is %"PRIu64" seconds)\n"
636 " -T, --tcp "SPACE"Send queries over TCP.\n"
637 " -Q, --qps <qps> "SPACE"Number of queries-per-second (approximately) to be sent.\n"
638 " "SPACE" (default is %"PRIu64" qps)\n"
639 " -b, --batch <size> "SPACE"Send queries in a batch of defined size.\n"
640 " "SPACE" (default is %d for UDP, %d for TCP)\n"
641 " -r, --drop "SPACE"Drop incoming responses (disables response statistics).\n"
642 " -p, --port <port> "SPACE"Remote destination port.\n"
643 " "SPACE" (default is %d)\n"
644 " -F, --affinity <spec> "SPACE"CPU affinity in the format [<cpu_start>][s<cpu_step>].\n"
645 " "SPACE" (default is %s)\n"
646 " -i, --infile <file> "SPACE"Path to a file with query templates.\n"
647 " -I, --interface <ifname> "SPACE"Override auto-detected interface for outgoing communication.\n"
648 " -l, --local <ip[/prefix]>"SPACE"Override auto-detected source IP address or subnet.\n"
649 " -h, --help "SPACE"Print the program help.\n"
650 " -V, --version "SPACE"Print the program version.\n"
651 "\n"
652 "Arguments:\n"
653 " <dest_ip> "SPACE"IPv4 or IPv6 address of the remote destination.\n",
654 PROGRAM_NAME, ctx_defaults.duration / 1000000, ctx_defaults.qps,
655 ctx_defaults.at_once, 1, LOCAL_PORT_DEFAULT, "0s1");
656 }
657
get_opts(int argc,char * argv[],xdp_gun_ctx_t * ctx)658 static bool get_opts(int argc, char *argv[], xdp_gun_ctx_t *ctx)
659 {
660 struct option opts[] = {
661 { "help", no_argument, NULL, 'h' },
662 { "version", no_argument, NULL, 'V' },
663 { "duration", required_argument, NULL, 't' },
664 { "qps", required_argument, NULL, 'Q' },
665 { "batch", required_argument, NULL, 'b' },
666 { "drop", no_argument, NULL, 'r' },
667 { "port", required_argument, NULL, 'p' },
668 { "tcp", no_argument, NULL, 'T' },
669 { "affinity", required_argument, NULL, 'F' },
670 { "interface", required_argument, NULL, 'I' },
671 { "local", required_argument, NULL, 'l' },
672 { "infile", required_argument, NULL, 'i' },
673 { NULL }
674 };
675
676 int opt = 0, arg;
677 bool default_at_once = true;
678 double argf;
679 char *argcp, *local_ip = NULL;
680 while ((opt = getopt_long(argc, argv, "hVt:Q:b:rp:TF:I:l:i:", opts, NULL)) != -1) {
681 switch (opt) {
682 case 'h':
683 print_help();
684 exit(EXIT_SUCCESS);
685 break;
686 case 'V':
687 print_version(PROGRAM_NAME);
688 exit(EXIT_SUCCESS);
689 break;
690 case 't':
691 argf = atof(optarg);
692 if (argf > 0) {
693 ctx->duration = argf * 1000000.0;
694 assert(ctx->duration >= 1000);
695 } else {
696 return false;
697 }
698 break;
699 case 'Q':
700 arg = atoi(optarg);
701 if (arg > 0) {
702 ctx->qps = arg;
703 } else {
704 return false;
705 }
706 break;
707 case 'b':
708 arg = atoi(optarg);
709 if (arg > 0) {
710 default_at_once = false;
711 ctx->at_once = arg;
712 } else {
713 return false;
714 }
715 break;
716 case 'r':
717 ctx->listen_port |= KNOT_XDP_LISTEN_PORT_DROP;
718 break;
719 case 'p':
720 arg = atoi(optarg);
721 if (arg > 0 && arg <= 0xffff) {
722 ctx->target_port = arg;
723 } else {
724 return false;
725 }
726 break;
727 case 'T':
728 ctx->tcp = true;
729 ctx->listen_port |= KNOT_XDP_LISTEN_PORT_TCP;
730 if (default_at_once) {
731 ctx->at_once = 1;
732 }
733 break;
734 case 'F':
735 if ((arg = atoi(optarg)) > 0) {
736 global_cpu_aff_start = arg;
737 }
738 argcp = strchr(optarg, 's');
739 if (argcp != NULL && (arg = atoi(argcp + 1)) > 0) {
740 global_cpu_aff_step = arg;
741 }
742 break;
743 case 'I':
744 strlcpy(ctx->dev, optarg, IFNAMSIZ);
745 break;
746 case 'l':
747 local_ip = optarg;
748 break;
749 case 'i':
750 if (!load_queries(optarg, ctx->edns_size, ctx->msgid)) {
751 return false;
752 }
753 break;
754 default:
755 return false;
756 }
757 }
758 if (global_payloads == NULL || argc - optind != 1 ||
759 !configure_target(argv[optind], local_ip, ctx)) {
760 return false;
761 }
762
763 if (ctx->qps < ctx->n_threads) {
764 printf("QPS must be at least the number of threads (%u)\n", ctx->n_threads);
765 return false;
766 }
767 ctx->qps /= ctx->n_threads;
768 printf("using interface %s, XDP threads %u\n", ctx->dev, ctx->n_threads);
769
770 return true;
771 }
772
main(int argc,char * argv[])773 int main(int argc, char *argv[])
774 {
775 xdp_gun_ctx_t ctx = ctx_defaults, *thread_ctxs = NULL;
776 ctx.msgid = time(NULL) % UINT16_MAX;
777 pthread_t *threads = NULL;
778
779 if (!get_opts(argc, argv, &ctx)) {
780 free_global_payloads();
781 return EXIT_FAILURE;
782 }
783
784 thread_ctxs = calloc(ctx.n_threads, sizeof(*thread_ctxs));
785 threads = calloc(ctx.n_threads, sizeof(*threads));
786 if (thread_ctxs == NULL || threads == NULL) {
787 printf("out of memory\n");
788 free(thread_ctxs);
789 free(threads);
790 free_global_payloads();
791 return EXIT_FAILURE;
792 }
793 for (int i = 0; i < ctx.n_threads; i++) {
794 thread_ctxs[i] = ctx;
795 thread_ctxs[i].thread_id = i;
796 }
797
798 if (!linux_at_least(5, 11)) {
799 struct rlimit min_limit = { RLIM_INFINITY, RLIM_INFINITY }, cur_limit = { 0 };
800 if (getrlimit(RLIMIT_MEMLOCK, &cur_limit) != 0 ||
801 cur_limit.rlim_cur != min_limit.rlim_cur ||
802 cur_limit.rlim_max != min_limit.rlim_max) {
803 int ret = setrlimit(RLIMIT_MEMLOCK, &min_limit);
804 if (ret != 0) {
805 printf("warning: unable to increase RLIMIT_MEMLOCK: %s\n",
806 strerror(errno));
807 }
808 }
809 }
810
811 pthread_mutex_init(&global_stats.mutex, NULL);
812
813 struct sigaction stop_action = { .sa_handler = sigterm_handler };
814 struct sigaction stats_action = { .sa_handler = sigusr_handler };
815 sigaction(SIGINT, &stop_action, NULL);
816 sigaction(SIGTERM, &stop_action, NULL);
817 sigaction(SIGUSR1, &stats_action, NULL);
818
819 for (size_t i = 0; i < ctx.n_threads; i++) {
820 unsigned affinity = global_cpu_aff_start + i * global_cpu_aff_step;
821 cpu_set_t set;
822 CPU_ZERO(&set);
823 CPU_SET(affinity, &set);
824 (void)pthread_create(&threads[i], NULL, xdp_gun_thread, &thread_ctxs[i]);
825 int ret = pthread_setaffinity_np(threads[i], sizeof(cpu_set_t), &set);
826 if (ret != 0) {
827 printf("failed to set affinity of thread#%zu to CPU#%u\n", i, affinity);
828 }
829 usleep(20000);
830 }
831 usleep(1000000);
832
833 xdp_trigger = KXDPGUN_START;
834 usleep(1000000);
835
836 for (size_t i = 0; i < ctx.n_threads; i++) {
837 pthread_join(threads[i], NULL);
838 }
839 if (global_stats.duration > 0 && global_stats.qry_sent > 0) {
840 print_stats(&global_stats, ctx.tcp, !(ctx.listen_port & KNOT_XDP_LISTEN_PORT_DROP));
841 }
842 pthread_mutex_destroy(&global_stats.mutex);
843
844 free(thread_ctxs);
845 free(threads);
846 free_global_payloads();
847 return EXIT_SUCCESS;
848 }
849