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