Linux Audio

Check our new training course

Loading...
Note: File does not exist in v5.4.
  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
 26static 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
 42void test__fail(void) { /* for network_helpers.c */ }
 43
 44static 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
 52static volatile bool exiting;
 53
 54static void sig_handler(int sig)
 55{
 56	exiting = true;
 57}
 58
 59const char *argp_program_version = "xdp-features 0.0";
 60const 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
 77static 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
 87static 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
113static 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
136static 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
204static const struct argp argp = {
205	.options = opts,
206	.parser = parse_arg,
207	.doc = argp_program_doc,
208};
209
210static 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
223static 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
252static 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
278static 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
339static 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
360static int dut_run(struct xdp_features *skel)
361{
362	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
363	int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
364	struct sockaddr_storage ctrl_addr;
365	pthread_t dut_thread = 0;
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
485end_thread:
486	pthread_join(dut_thread, NULL);
487out:
488	bpf_xdp_detach(env.ifindex, flags, NULL);
489	close(ctrl_sockfd);
490	free_fds(sockfd, 1);
491
492	return err;
493}
494
495static 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
528static 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
549static 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
573static 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"));
649out:
650	bpf_xdp_detach(env.ifindex, flags, NULL);
651	close(sockfd);
652	return err < 0 ? err : 0;
653}
654
655int 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
714cleanup:
715	xdp_features__destroy(skel);
716
717	return err < 0 ? -err : 0;
718}