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