1 // SPDX-License-Identifier: GPL-2.0
2 #include <uapi/linux/bpf.h>
3 #include <uapi/linux/netdev.h>
4 #include <linux/if_link.h>
5 #include <signal.h>
6 #include <argp.h>
7 #include <net/if.h>
8 #include <sys/socket.h>
9 #include <netinet/in.h>
10 #include <netinet/tcp.h>
11 #include <unistd.h>
12 #include <arpa/inet.h>
13 #include <bpf/bpf.h>
14 #include <bpf/libbpf.h>
15 #include <pthread.h>
16 
17 #include <network_helpers.h>
18 
19 #include "xdp_features.skel.h"
20 #include "xdp_features.h"
21 
22 #define RED(str)	"\033[0;31m" str "\033[0m"
23 #define GREEN(str)	"\033[0;32m" str "\033[0m"
24 #define YELLOW(str)	"\033[0;33m" str "\033[0m"
25 
26 static struct env {
27 	bool verbosity;
28 	int ifindex;
29 	bool is_tester;
30 	struct {
31 		enum netdev_xdp_act drv_feature;
32 		enum xdp_action action;
33 	} feature;
34 	struct sockaddr_storage dut_ctrl_addr;
35 	struct sockaddr_storage dut_addr;
36 	struct sockaddr_storage tester_addr;
37 } env;
38 
39 #define BUFSIZE		128
40 
41 void test__fail(void) { /* for network_helpers.c */ }
42 
43 static int libbpf_print_fn(enum libbpf_print_level level,
44 			   const char *format, va_list args)
45 {
46 	if (level == LIBBPF_DEBUG && !env.verbosity)
47 		return 0;
48 	return vfprintf(stderr, format, args);
49 }
50 
51 static volatile bool exiting;
52 
53 static void sig_handler(int sig)
54 {
55 	exiting = true;
56 }
57 
58 const char *argp_program_version = "xdp-features 0.0";
59 const char argp_program_doc[] =
60 "XDP features detection application.\n"
61 "\n"
62 "XDP features application checks the XDP advertised features match detected ones.\n"
63 "\n"
64 "USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n"
65 "\n"
66 "dut-data-ip, tester-data-ip, dut-ctrl-ip: IPv6 or IPv4-mapped-IPv6 addresses;\n"
67 "\n"
68 "XDP features\n:"
69 "- XDP_PASS\n"
70 "- XDP_DROP\n"
71 "- XDP_ABORTED\n"
72 "- XDP_REDIRECT\n"
73 "- XDP_NDO_XMIT\n"
74 "- XDP_TX\n";
75 
76 static const struct argp_option opts[] = {
77 	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
78 	{ "tester", 't', NULL, 0, "Tester mode" },
79 	{ "feature", 'f', "XDP-FEATURE", 0, "XDP feature to test" },
80 	{ "dut_data_ip", 'D', "DUT-DATA-IP", 0, "DUT IP data channel" },
81 	{ "dut_ctrl_ip", 'C', "DUT-CTRL-IP", 0, "DUT IP control channel" },
82 	{ "tester_data_ip", 'T', "TESTER-DATA-IP", 0, "Tester IP data channel" },
83 	{},
84 };
85 
86 static int get_xdp_feature(const char *arg)
87 {
88 	if (!strcmp(arg, "XDP_PASS")) {
89 		env.feature.action = XDP_PASS;
90 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
91 	} else if (!strcmp(arg, "XDP_DROP")) {
92 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
93 		env.feature.action = XDP_DROP;
94 	} else if (!strcmp(arg, "XDP_ABORTED")) {
95 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
96 		env.feature.action = XDP_ABORTED;
97 	} else if (!strcmp(arg, "XDP_TX")) {
98 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
99 		env.feature.action = XDP_TX;
100 	} else if (!strcmp(arg, "XDP_REDIRECT")) {
101 		env.feature.drv_feature = NETDEV_XDP_ACT_REDIRECT;
102 		env.feature.action = XDP_REDIRECT;
103 	} else if (!strcmp(arg, "XDP_NDO_XMIT")) {
104 		env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
105 	} else {
106 		return -EINVAL;
107 	}
108 
109 	return 0;
110 }
111 
112 static char *get_xdp_feature_str(void)
113 {
114 	switch (env.feature.action) {
115 	case XDP_PASS:
116 		return YELLOW("XDP_PASS");
117 	case XDP_DROP:
118 		return YELLOW("XDP_DROP");
119 	case XDP_ABORTED:
120 		return YELLOW("XDP_ABORTED");
121 	case XDP_TX:
122 		return YELLOW("XDP_TX");
123 	case XDP_REDIRECT:
124 		return YELLOW("XDP_REDIRECT");
125 	default:
126 		break;
127 	}
128 
129 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
130 		return YELLOW("XDP_NDO_XMIT");
131 
132 	return "";
133 }
134 
135 static error_t parse_arg(int key, char *arg, struct argp_state *state)
136 {
137 	switch (key) {
138 	case 'v':
139 		env.verbosity = true;
140 		break;
141 	case 't':
142 		env.is_tester = true;
143 		break;
144 	case 'f':
145 		if (get_xdp_feature(arg) < 0) {
146 			fprintf(stderr, "Invalid xdp feature: %s\n", arg);
147 			argp_usage(state);
148 			return ARGP_ERR_UNKNOWN;
149 		}
150 		break;
151 	case 'D':
152 		if (make_sockaddr(AF_INET6, arg, DUT_ECHO_PORT,
153 				  &env.dut_addr, NULL)) {
154 			fprintf(stderr, "Invalid DUT address: %s\n", arg);
155 			return ARGP_ERR_UNKNOWN;
156 		}
157 		break;
158 	case 'C':
159 		if (make_sockaddr(AF_INET6, arg, DUT_CTRL_PORT,
160 				  &env.dut_ctrl_addr, NULL)) {
161 			fprintf(stderr, "Invalid DUT CTRL address: %s\n", arg);
162 			return ARGP_ERR_UNKNOWN;
163 		}
164 		break;
165 	case 'T':
166 		if (make_sockaddr(AF_INET6, arg, 0, &env.tester_addr, NULL)) {
167 			fprintf(stderr, "Invalid Tester address: %s\n", arg);
168 			return ARGP_ERR_UNKNOWN;
169 		}
170 		break;
171 	case ARGP_KEY_ARG:
172 		errno = 0;
173 		if (strlen(arg) >= IF_NAMESIZE) {
174 			fprintf(stderr, "Invalid device name: %s\n", arg);
175 			argp_usage(state);
176 			return ARGP_ERR_UNKNOWN;
177 		}
178 
179 		env.ifindex = if_nametoindex(arg);
180 		if (!env.ifindex)
181 			env.ifindex = strtoul(arg, NULL, 0);
182 		if (!env.ifindex) {
183 			fprintf(stderr,
184 				"Bad interface index or name (%d): %s\n",
185 				errno, strerror(errno));
186 			argp_usage(state);
187 			return ARGP_ERR_UNKNOWN;
188 		}
189 		break;
190 	default:
191 		return ARGP_ERR_UNKNOWN;
192 	}
193 
194 	return 0;
195 }
196 
197 static const struct argp argp = {
198 	.options = opts,
199 	.parser = parse_arg,
200 	.doc = argp_program_doc,
201 };
202 
203 static void set_env_default(void)
204 {
205 	env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
206 	env.feature.action = -EINVAL;
207 	env.ifindex = -ENODEV;
208 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_CTRL_PORT,
209 		      &env.dut_ctrl_addr, NULL);
210 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_ECHO_PORT,
211 		      &env.dut_addr, NULL);
212 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", 0, &env.tester_addr, NULL);
213 }
214 
215 static void *dut_echo_thread(void *arg)
216 {
217 	unsigned char buf[sizeof(struct tlv_hdr)];
218 	int sockfd = *(int *)arg;
219 
220 	while (!exiting) {
221 		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
222 		struct sockaddr_storage addr;
223 		socklen_t addrlen;
224 		size_t n;
225 
226 		n = recvfrom(sockfd, buf, sizeof(buf), MSG_WAITALL,
227 			     (struct sockaddr *)&addr, &addrlen);
228 		if (n != ntohs(tlv->len))
229 			continue;
230 
231 		if (ntohs(tlv->type) != CMD_ECHO)
232 			continue;
233 
234 		sendto(sockfd, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM,
235 		       (struct sockaddr *)&addr, addrlen);
236 	}
237 
238 	pthread_exit((void *)0);
239 	close(sockfd);
240 
241 	return NULL;
242 }
243 
244 static int dut_run_echo_thread(pthread_t *t, int *sockfd)
245 {
246 	int err;
247 
248 	sockfd = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
249 					DUT_ECHO_PORT, 0, 1);
250 	if (!sockfd) {
251 		fprintf(stderr, "Failed to create echo socket\n");
252 		return -errno;
253 	}
254 
255 	/* start echo channel */
256 	err = pthread_create(t, NULL, dut_echo_thread, sockfd);
257 	if (err) {
258 		fprintf(stderr, "Failed creating dut_echo thread: %s\n",
259 			strerror(-err));
260 		free_fds(sockfd, 1);
261 		return -EINVAL;
262 	}
263 
264 	return 0;
265 }
266 
267 static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
268 {
269 	enum xdp_action action = env.feature.action;
270 	struct bpf_program *prog;
271 	unsigned int key = 0;
272 	int err, fd = 0;
273 
274 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) {
275 		struct bpf_devmap_val entry = {
276 			.ifindex = env.ifindex,
277 		};
278 
279 		err = bpf_map__update_elem(skel->maps.dev_map,
280 					   &key, sizeof(key),
281 					   &entry, sizeof(entry), 0);
282 		if (err < 0)
283 			return err;
284 
285 		fd = bpf_program__fd(skel->progs.xdp_do_redirect_cpumap);
286 		action = XDP_REDIRECT;
287 	}
288 
289 	switch (action) {
290 	case XDP_TX:
291 		prog = skel->progs.xdp_do_tx;
292 		break;
293 	case XDP_DROP:
294 		prog = skel->progs.xdp_do_drop;
295 		break;
296 	case XDP_ABORTED:
297 		prog = skel->progs.xdp_do_aborted;
298 		break;
299 	case XDP_PASS:
300 		prog = skel->progs.xdp_do_pass;
301 		break;
302 	case XDP_REDIRECT: {
303 		struct bpf_cpumap_val entry = {
304 			.qsize = 2048,
305 			.bpf_prog.fd = fd,
306 		};
307 
308 		err = bpf_map__update_elem(skel->maps.cpu_map,
309 					   &key, sizeof(key),
310 					   &entry, sizeof(entry), 0);
311 		if (err < 0)
312 			return err;
313 
314 		prog = skel->progs.xdp_do_redirect;
315 		break;
316 	}
317 	default:
318 		return -EINVAL;
319 	}
320 
321 	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
322 	if (err)
323 		fprintf(stderr,
324 			"Failed to attach XDP program to ifindex %d\n",
325 			env.ifindex);
326 	return err;
327 }
328 
329 static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
330 		    size_t val_size)
331 {
332 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
333 	size_t len;
334 
335 	len = recv(sockfd, buf, bufsize, 0);
336 	if (len != ntohs(tlv->len) || len < sizeof(*tlv))
337 		return -EINVAL;
338 
339 	if (val) {
340 		len -= sizeof(*tlv);
341 		if (len > val_size)
342 			return -ENOMEM;
343 
344 		memcpy(val, tlv->data, len);
345 	}
346 
347 	return 0;
348 }
349 
350 static int dut_run(struct xdp_features *skel)
351 {
352 	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
353 	int state, err, *sockfd, ctrl_sockfd, echo_sockfd;
354 	struct sockaddr_storage ctrl_addr;
355 	pthread_t dut_thread;
356 	socklen_t addrlen;
357 
358 	sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
359 					DUT_CTRL_PORT, 0, 1);
360 	if (!sockfd) {
361 		fprintf(stderr, "Failed to create DUT socket\n");
362 		return -errno;
363 	}
364 
365 	ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen);
366 	if (ctrl_sockfd < 0) {
367 		fprintf(stderr, "Failed to accept connection on DUT socket\n");
368 		free_fds(sockfd, 1);
369 		return -errno;
370 	}
371 
372 	/* CTRL loop */
373 	while (!exiting) {
374 		unsigned char buf[BUFSIZE] = {};
375 		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
376 
377 		err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
378 		if (err)
379 			continue;
380 
381 		switch (ntohs(tlv->type)) {
382 		case CMD_START: {
383 			if (state == CMD_START)
384 				continue;
385 
386 			state = CMD_START;
387 			/* Load the XDP program on the DUT */
388 			err = dut_attach_xdp_prog(skel, flags);
389 			if (err)
390 				goto out;
391 
392 			err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
393 			if (err < 0)
394 				goto out;
395 
396 			tlv->type = htons(CMD_ACK);
397 			tlv->len = htons(sizeof(*tlv));
398 			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
399 			if (err < 0)
400 				goto end_thread;
401 			break;
402 		}
403 		case CMD_STOP:
404 			if (state != CMD_START)
405 				break;
406 
407 			state = CMD_STOP;
408 
409 			exiting = true;
410 			bpf_xdp_detach(env.ifindex, flags, NULL);
411 
412 			tlv->type = htons(CMD_ACK);
413 			tlv->len = htons(sizeof(*tlv));
414 			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
415 			goto end_thread;
416 		case CMD_GET_XDP_CAP: {
417 			LIBBPF_OPTS(bpf_xdp_query_opts, opts);
418 			unsigned long long val;
419 			size_t n;
420 
421 			err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE,
422 					    &opts);
423 			if (err) {
424 				fprintf(stderr,
425 					"Failed to query XDP cap for ifindex %d\n",
426 					env.ifindex);
427 				goto end_thread;
428 			}
429 
430 			tlv->type = htons(CMD_ACK);
431 			n = sizeof(*tlv) + sizeof(opts.feature_flags);
432 			tlv->len = htons(n);
433 
434 			val = htobe64(opts.feature_flags);
435 			memcpy(tlv->data, &val, sizeof(val));
436 
437 			err = send(ctrl_sockfd, buf, n, 0);
438 			if (err < 0)
439 				goto end_thread;
440 			break;
441 		}
442 		case CMD_GET_STATS: {
443 			unsigned int key = 0, val;
444 			size_t n;
445 
446 			err = bpf_map__lookup_elem(skel->maps.dut_stats,
447 						   &key, sizeof(key),
448 						   &val, sizeof(val), 0);
449 			if (err) {
450 				fprintf(stderr, "bpf_map_lookup_elem failed\n");
451 				goto end_thread;
452 			}
453 
454 			tlv->type = htons(CMD_ACK);
455 			n = sizeof(*tlv) + sizeof(val);
456 			tlv->len = htons(n);
457 
458 			val = htonl(val);
459 			memcpy(tlv->data, &val, sizeof(val));
460 
461 			err = send(ctrl_sockfd, buf, n, 0);
462 			if (err < 0)
463 				goto end_thread;
464 			break;
465 		}
466 		default:
467 			break;
468 		}
469 	}
470 
471 end_thread:
472 	pthread_join(dut_thread, NULL);
473 out:
474 	bpf_xdp_detach(env.ifindex, flags, NULL);
475 	close(ctrl_sockfd);
476 	free_fds(sockfd, 1);
477 
478 	return err;
479 }
480 
481 static bool tester_collect_detected_cap(struct xdp_features *skel,
482 					unsigned int dut_stats)
483 {
484 	unsigned int err, key = 0, val;
485 
486 	if (!dut_stats)
487 		return false;
488 
489 	err = bpf_map__lookup_elem(skel->maps.stats, &key, sizeof(key),
490 				   &val, sizeof(val), 0);
491 	if (err) {
492 		fprintf(stderr, "bpf_map_lookup_elem failed\n");
493 		return false;
494 	}
495 
496 	switch (env.feature.action) {
497 	case XDP_PASS:
498 	case XDP_TX:
499 	case XDP_REDIRECT:
500 		return val > 0;
501 	case XDP_DROP:
502 	case XDP_ABORTED:
503 		return val == 0;
504 	default:
505 		break;
506 	}
507 
508 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
509 		return val > 0;
510 
511 	return false;
512 }
513 
514 static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val,
515 			     size_t val_size)
516 {
517 	unsigned char buf[BUFSIZE] = {};
518 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
519 	int err;
520 
521 	tlv->type = htons(cmd);
522 	tlv->len = htons(sizeof(*tlv));
523 
524 	err = send(sockfd, buf, sizeof(*tlv), 0);
525 	if (err < 0)
526 		return err;
527 
528 	err = recv_msg(sockfd, buf, BUFSIZE, val, val_size);
529 	if (err < 0)
530 		return err;
531 
532 	return ntohs(tlv->type) == CMD_ACK ? 0 : -EINVAL;
533 }
534 
535 static int send_echo_msg(void)
536 {
537 	unsigned char buf[sizeof(struct tlv_hdr)];
538 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
539 	int sockfd, n;
540 
541 	sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
542 	if (sockfd < 0) {
543 		fprintf(stderr, "Failed to create echo socket\n");
544 		return -errno;
545 	}
546 
547 	tlv->type = htons(CMD_ECHO);
548 	tlv->len = htons(sizeof(*tlv));
549 
550 	n = sendto(sockfd, buf, sizeof(*tlv), MSG_NOSIGNAL | MSG_CONFIRM,
551 		   (struct sockaddr *)&env.dut_addr, sizeof(env.dut_addr));
552 	close(sockfd);
553 
554 	return n == ntohs(tlv->len) ? 0 : -EINVAL;
555 }
556 
557 static int tester_run(struct xdp_features *skel)
558 {
559 	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
560 	unsigned long long advertised_feature;
561 	struct bpf_program *prog;
562 	unsigned int stats;
563 	int i, err, sockfd;
564 	bool detected_cap;
565 
566 	sockfd = socket(AF_INET6, SOCK_STREAM, 0);
567 	if (sockfd < 0) {
568 		fprintf(stderr, "Failed to create tester socket\n");
569 		return -errno;
570 	}
571 
572 	if (settimeo(sockfd, 1000) < 0)
573 		return -EINVAL;
574 
575 	err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
576 		      sizeof(env.dut_ctrl_addr));
577 	if (err) {
578 		fprintf(stderr, "Failed to connect to the DUT\n");
579 		return -errno;
580 	}
581 
582 	err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
583 				sizeof(advertised_feature));
584 	if (err < 0) {
585 		close(sockfd);
586 		return err;
587 	}
588 
589 	advertised_feature = be64toh(advertised_feature);
590 
591 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT ||
592 	    env.feature.action == XDP_TX)
593 		prog = skel->progs.xdp_tester_check_tx;
594 	else
595 		prog = skel->progs.xdp_tester_check_rx;
596 
597 	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
598 	if (err) {
599 		fprintf(stderr, "Failed to attach XDP program to ifindex %d\n",
600 			env.ifindex);
601 		goto out;
602 	}
603 
604 	err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);
605 	if (err)
606 		goto out;
607 
608 	for (i = 0; i < 10 && !exiting; i++) {
609 		err = send_echo_msg();
610 		if (err < 0)
611 			goto out;
612 
613 		sleep(1);
614 	}
615 
616 	err = send_and_recv_msg(sockfd, CMD_GET_STATS, &stats, sizeof(stats));
617 	if (err)
618 		goto out;
619 
620 	/* stop the test */
621 	err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 0);
622 	/* send a new echo message to wake echo thread of the dut */
623 	send_echo_msg();
624 
625 	detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
626 
627 	fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
628 		detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
629 		env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
630 							     : RED("NOT ADVERTISED"));
631 out:
632 	bpf_xdp_detach(env.ifindex, flags, NULL);
633 	close(sockfd);
634 	return err < 0 ? err : 0;
635 }
636 
637 int main(int argc, char **argv)
638 {
639 	struct xdp_features *skel;
640 	int err;
641 
642 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
643 	libbpf_set_print(libbpf_print_fn);
644 
645 	signal(SIGINT, sig_handler);
646 	signal(SIGTERM, sig_handler);
647 
648 	set_env_default();
649 
650 	/* Parse command line arguments */
651 	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
652 	if (err)
653 		return err;
654 
655 	if (env.ifindex < 0) {
656 		fprintf(stderr, "Invalid ifindex\n");
657 		return -ENODEV;
658 	}
659 
660 	/* Load and verify BPF application */
661 	skel = xdp_features__open();
662 	if (!skel) {
663 		fprintf(stderr, "Failed to open and load BPF skeleton\n");
664 		return -EINVAL;
665 	}
666 
667 	skel->rodata->tester_addr =
668 		((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr;
669 	skel->rodata->dut_addr =
670 		((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr;
671 
672 	/* Load & verify BPF programs */
673 	err = xdp_features__load(skel);
674 	if (err) {
675 		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
676 		goto cleanup;
677 	}
678 
679 	err = xdp_features__attach(skel);
680 	if (err) {
681 		fprintf(stderr, "Failed to attach BPF skeleton\n");
682 		goto cleanup;
683 	}
684 
685 	if (env.is_tester) {
686 		/* Tester */
687 		fprintf(stdout, "Starting tester on device %d\n", env.ifindex);
688 		err = tester_run(skel);
689 	} else {
690 		/* DUT */
691 		fprintf(stdout, "Starting DUT on device %d\n", env.ifindex);
692 		err = dut_run(skel);
693 	}
694 
695 cleanup:
696 	xdp_features__destroy(skel);
697 
698 	return err < 0 ? -err : 0;
699 }
700