719 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			719 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| #include <uapi/linux/bpf.h>
 | |
| #include <uapi/linux/netdev.h>
 | |
| #include <linux/if_link.h>
 | |
| #include <signal.h>
 | |
| #include <argp.h>
 | |
| #include <net/if.h>
 | |
| #include <sys/socket.h>
 | |
| #include <netinet/in.h>
 | |
| #include <netinet/tcp.h>
 | |
| #include <unistd.h>
 | |
| #include <arpa/inet.h>
 | |
| #include <bpf/bpf.h>
 | |
| #include <bpf/libbpf.h>
 | |
| #include <pthread.h>
 | |
| 
 | |
| #include <network_helpers.h>
 | |
| 
 | |
| #include "xdp_features.skel.h"
 | |
| #include "xdp_features.h"
 | |
| 
 | |
| #define RED(str)	"\033[0;31m" str "\033[0m"
 | |
| #define GREEN(str)	"\033[0;32m" str "\033[0m"
 | |
| #define YELLOW(str)	"\033[0;33m" str "\033[0m"
 | |
| 
 | |
| static struct env {
 | |
| 	bool verbosity;
 | |
| 	char ifname[IF_NAMESIZE];
 | |
| 	int ifindex;
 | |
| 	bool is_tester;
 | |
| 	struct {
 | |
| 		enum netdev_xdp_act drv_feature;
 | |
| 		enum xdp_action action;
 | |
| 	} feature;
 | |
| 	struct sockaddr_storage dut_ctrl_addr;
 | |
| 	struct sockaddr_storage dut_addr;
 | |
| 	struct sockaddr_storage tester_addr;
 | |
| } env;
 | |
| 
 | |
| #define BUFSIZE		128
 | |
| 
 | |
| void test__fail(void) { /* for network_helpers.c */ }
 | |
| 
 | |
| static int libbpf_print_fn(enum libbpf_print_level level,
 | |
| 			   const char *format, va_list args)
 | |
| {
 | |
| 	if (level == LIBBPF_DEBUG && !env.verbosity)
 | |
| 		return 0;
 | |
| 	return vfprintf(stderr, format, args);
 | |
| }
 | |
| 
 | |
| static volatile bool exiting;
 | |
| 
 | |
| static void sig_handler(int sig)
 | |
| {
 | |
| 	exiting = true;
 | |
| }
 | |
| 
 | |
| const char *argp_program_version = "xdp-features 0.0";
 | |
| const char argp_program_doc[] =
 | |
| "XDP features detection application.\n"
 | |
| "\n"
 | |
| "XDP features application checks the XDP advertised features match detected ones.\n"
 | |
| "\n"
 | |
| "USAGE: ./xdp-features [-vt] [-f <xdp-feature>] [-D <dut-data-ip>] [-T <tester-data-ip>] [-C <dut-ctrl-ip>] <iface-name>\n"
 | |
| "\n"
 | |
| "dut-data-ip, tester-data-ip, dut-ctrl-ip: IPv6 or IPv4-mapped-IPv6 addresses;\n"
 | |
| "\n"
 | |
| "XDP features\n:"
 | |
| "- XDP_PASS\n"
 | |
| "- XDP_DROP\n"
 | |
| "- XDP_ABORTED\n"
 | |
| "- XDP_REDIRECT\n"
 | |
| "- XDP_NDO_XMIT\n"
 | |
| "- XDP_TX\n";
 | |
| 
 | |
| static const struct argp_option opts[] = {
 | |
| 	{ "verbose", 'v', NULL, 0, "Verbose debug output" },
 | |
| 	{ "tester", 't', NULL, 0, "Tester mode" },
 | |
| 	{ "feature", 'f', "XDP-FEATURE", 0, "XDP feature to test" },
 | |
| 	{ "dut_data_ip", 'D', "DUT-DATA-IP", 0, "DUT IP data channel" },
 | |
| 	{ "dut_ctrl_ip", 'C', "DUT-CTRL-IP", 0, "DUT IP control channel" },
 | |
| 	{ "tester_data_ip", 'T', "TESTER-DATA-IP", 0, "Tester IP data channel" },
 | |
| 	{},
 | |
| };
 | |
| 
 | |
| static int get_xdp_feature(const char *arg)
 | |
| {
 | |
| 	if (!strcmp(arg, "XDP_PASS")) {
 | |
| 		env.feature.action = XDP_PASS;
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
 | |
| 	} else if (!strcmp(arg, "XDP_DROP")) {
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
 | |
| 		env.feature.action = XDP_DROP;
 | |
| 	} else if (!strcmp(arg, "XDP_ABORTED")) {
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
 | |
| 		env.feature.action = XDP_ABORTED;
 | |
| 	} else if (!strcmp(arg, "XDP_TX")) {
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_BASIC;
 | |
| 		env.feature.action = XDP_TX;
 | |
| 	} else if (!strcmp(arg, "XDP_REDIRECT")) {
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_REDIRECT;
 | |
| 		env.feature.action = XDP_REDIRECT;
 | |
| 	} else if (!strcmp(arg, "XDP_NDO_XMIT")) {
 | |
| 		env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
 | |
| 	} else {
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static char *get_xdp_feature_str(void)
 | |
| {
 | |
| 	switch (env.feature.action) {
 | |
| 	case XDP_PASS:
 | |
| 		return YELLOW("XDP_PASS");
 | |
| 	case XDP_DROP:
 | |
| 		return YELLOW("XDP_DROP");
 | |
| 	case XDP_ABORTED:
 | |
| 		return YELLOW("XDP_ABORTED");
 | |
| 	case XDP_TX:
 | |
| 		return YELLOW("XDP_TX");
 | |
| 	case XDP_REDIRECT:
 | |
| 		return YELLOW("XDP_REDIRECT");
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
 | |
| 		return YELLOW("XDP_NDO_XMIT");
 | |
| 
 | |
| 	return "";
 | |
| }
 | |
| 
 | |
| static error_t parse_arg(int key, char *arg, struct argp_state *state)
 | |
| {
 | |
| 	switch (key) {
 | |
| 	case 'v':
 | |
| 		env.verbosity = true;
 | |
| 		break;
 | |
| 	case 't':
 | |
| 		env.is_tester = true;
 | |
| 		break;
 | |
| 	case 'f':
 | |
| 		if (get_xdp_feature(arg) < 0) {
 | |
| 			fprintf(stderr, "Invalid xdp feature: %s\n", arg);
 | |
| 			argp_usage(state);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	case 'D':
 | |
| 		if (make_sockaddr(AF_INET6, arg, DUT_ECHO_PORT,
 | |
| 				  &env.dut_addr, NULL)) {
 | |
| 			fprintf(stderr,
 | |
| 				"Invalid address assigned to the Device Under Test: %s\n",
 | |
| 				arg);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	case 'C':
 | |
| 		if (make_sockaddr(AF_INET6, arg, DUT_CTRL_PORT,
 | |
| 				  &env.dut_ctrl_addr, NULL)) {
 | |
| 			fprintf(stderr,
 | |
| 				"Invalid address assigned to the Device Under Test: %s\n",
 | |
| 				arg);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	case 'T':
 | |
| 		if (make_sockaddr(AF_INET6, arg, 0, &env.tester_addr, NULL)) {
 | |
| 			fprintf(stderr,
 | |
| 				"Invalid address assigned to the Tester device: %s\n",
 | |
| 				arg);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	case ARGP_KEY_ARG:
 | |
| 		errno = 0;
 | |
| 		if (strlen(arg) >= IF_NAMESIZE) {
 | |
| 			fprintf(stderr, "Invalid device name: %s\n", arg);
 | |
| 			argp_usage(state);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 
 | |
| 		env.ifindex = if_nametoindex(arg);
 | |
| 		if (!env.ifindex)
 | |
| 			env.ifindex = strtoul(arg, NULL, 0);
 | |
| 		if (!env.ifindex || !if_indextoname(env.ifindex, env.ifname)) {
 | |
| 			fprintf(stderr,
 | |
| 				"Bad interface index or name (%d): %s\n",
 | |
| 				errno, strerror(errno));
 | |
| 			argp_usage(state);
 | |
| 			return ARGP_ERR_UNKNOWN;
 | |
| 		}
 | |
| 		break;
 | |
| 	default:
 | |
| 		return ARGP_ERR_UNKNOWN;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct argp argp = {
 | |
| 	.options = opts,
 | |
| 	.parser = parse_arg,
 | |
| 	.doc = argp_program_doc,
 | |
| };
 | |
| 
 | |
| static void set_env_default(void)
 | |
| {
 | |
| 	env.feature.drv_feature = NETDEV_XDP_ACT_NDO_XMIT;
 | |
| 	env.feature.action = -EINVAL;
 | |
| 	env.ifindex = -ENODEV;
 | |
| 	strcpy(env.ifname, "unknown");
 | |
| 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_CTRL_PORT,
 | |
| 		      &env.dut_ctrl_addr, NULL);
 | |
| 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", DUT_ECHO_PORT,
 | |
| 		      &env.dut_addr, NULL);
 | |
| 	make_sockaddr(AF_INET6, "::ffff:127.0.0.1", 0, &env.tester_addr, NULL);
 | |
| }
 | |
| 
 | |
| static void *dut_echo_thread(void *arg)
 | |
| {
 | |
| 	unsigned char buf[sizeof(struct tlv_hdr)];
 | |
| 	int sockfd = *(int *)arg;
 | |
| 
 | |
| 	while (!exiting) {
 | |
| 		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
 | |
| 		struct sockaddr_storage addr;
 | |
| 		socklen_t addrlen;
 | |
| 		size_t n;
 | |
| 
 | |
| 		n = recvfrom(sockfd, buf, sizeof(buf), MSG_WAITALL,
 | |
| 			     (struct sockaddr *)&addr, &addrlen);
 | |
| 		if (n != ntohs(tlv->len))
 | |
| 			continue;
 | |
| 
 | |
| 		if (ntohs(tlv->type) != CMD_ECHO)
 | |
| 			continue;
 | |
| 
 | |
| 		sendto(sockfd, buf, sizeof(buf), MSG_NOSIGNAL | MSG_CONFIRM,
 | |
| 		       (struct sockaddr *)&addr, addrlen);
 | |
| 	}
 | |
| 
 | |
| 	pthread_exit((void *)0);
 | |
| 	close(sockfd);
 | |
| 
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| static int dut_run_echo_thread(pthread_t *t, int *sockfd)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	sockfd = start_reuseport_server(AF_INET6, SOCK_DGRAM, NULL,
 | |
| 					DUT_ECHO_PORT, 0, 1);
 | |
| 	if (!sockfd) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed creating data UDP socket on device %s\n",
 | |
| 			env.ifname);
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	/* start echo channel */
 | |
| 	err = pthread_create(t, NULL, dut_echo_thread, sockfd);
 | |
| 	if (err) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed creating data UDP thread on device %s: %s\n",
 | |
| 			env.ifname, strerror(-err));
 | |
| 		free_fds(sockfd, 1);
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int dut_attach_xdp_prog(struct xdp_features *skel, int flags)
 | |
| {
 | |
| 	enum xdp_action action = env.feature.action;
 | |
| 	struct bpf_program *prog;
 | |
| 	unsigned int key = 0;
 | |
| 	int err, fd = 0;
 | |
| 
 | |
| 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT) {
 | |
| 		struct bpf_devmap_val entry = {
 | |
| 			.ifindex = env.ifindex,
 | |
| 		};
 | |
| 
 | |
| 		err = bpf_map__update_elem(skel->maps.dev_map,
 | |
| 					   &key, sizeof(key),
 | |
| 					   &entry, sizeof(entry), 0);
 | |
| 		if (err < 0)
 | |
| 			return err;
 | |
| 
 | |
| 		fd = bpf_program__fd(skel->progs.xdp_do_redirect_cpumap);
 | |
| 		action = XDP_REDIRECT;
 | |
| 	}
 | |
| 
 | |
| 	switch (action) {
 | |
| 	case XDP_TX:
 | |
| 		prog = skel->progs.xdp_do_tx;
 | |
| 		break;
 | |
| 	case XDP_DROP:
 | |
| 		prog = skel->progs.xdp_do_drop;
 | |
| 		break;
 | |
| 	case XDP_ABORTED:
 | |
| 		prog = skel->progs.xdp_do_aborted;
 | |
| 		break;
 | |
| 	case XDP_PASS:
 | |
| 		prog = skel->progs.xdp_do_pass;
 | |
| 		break;
 | |
| 	case XDP_REDIRECT: {
 | |
| 		struct bpf_cpumap_val entry = {
 | |
| 			.qsize = 2048,
 | |
| 			.bpf_prog.fd = fd,
 | |
| 		};
 | |
| 
 | |
| 		err = bpf_map__update_elem(skel->maps.cpu_map,
 | |
| 					   &key, sizeof(key),
 | |
| 					   &entry, sizeof(entry), 0);
 | |
| 		if (err < 0)
 | |
| 			return err;
 | |
| 
 | |
| 		prog = skel->progs.xdp_do_redirect;
 | |
| 		break;
 | |
| 	}
 | |
| 	default:
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
 | |
| 	if (err)
 | |
| 		fprintf(stderr, "Failed attaching XDP program to device %s\n",
 | |
| 			env.ifname);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int recv_msg(int sockfd, void *buf, size_t bufsize, void *val,
 | |
| 		    size_t val_size)
 | |
| {
 | |
| 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
 | |
| 	size_t len;
 | |
| 
 | |
| 	len = recv(sockfd, buf, bufsize, 0);
 | |
| 	if (len != ntohs(tlv->len) || len < sizeof(*tlv))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (val) {
 | |
| 		len -= sizeof(*tlv);
 | |
| 		if (len > val_size)
 | |
| 			return -ENOMEM;
 | |
| 
 | |
| 		memcpy(val, tlv->data, len);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int dut_run(struct xdp_features *skel)
 | |
| {
 | |
| 	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
 | |
| 	int state, err = 0, *sockfd, ctrl_sockfd, echo_sockfd;
 | |
| 	struct sockaddr_storage ctrl_addr;
 | |
| 	pthread_t dut_thread = 0;
 | |
| 	socklen_t addrlen;
 | |
| 
 | |
| 	sockfd = start_reuseport_server(AF_INET6, SOCK_STREAM, NULL,
 | |
| 					DUT_CTRL_PORT, 0, 1);
 | |
| 	if (!sockfd) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed creating control socket on device %s\n", env.ifname);
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	ctrl_sockfd = accept(*sockfd, (struct sockaddr *)&ctrl_addr, &addrlen);
 | |
| 	if (ctrl_sockfd < 0) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed accepting connections on device %s control socket\n",
 | |
| 			env.ifname);
 | |
| 		free_fds(sockfd, 1);
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	/* CTRL loop */
 | |
| 	while (!exiting) {
 | |
| 		unsigned char buf[BUFSIZE] = {};
 | |
| 		struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
 | |
| 
 | |
| 		err = recv_msg(ctrl_sockfd, buf, BUFSIZE, NULL, 0);
 | |
| 		if (err)
 | |
| 			continue;
 | |
| 
 | |
| 		switch (ntohs(tlv->type)) {
 | |
| 		case CMD_START: {
 | |
| 			if (state == CMD_START)
 | |
| 				continue;
 | |
| 
 | |
| 			state = CMD_START;
 | |
| 			/* Load the XDP program on the DUT */
 | |
| 			err = dut_attach_xdp_prog(skel, flags);
 | |
| 			if (err)
 | |
| 				goto out;
 | |
| 
 | |
| 			err = dut_run_echo_thread(&dut_thread, &echo_sockfd);
 | |
| 			if (err < 0)
 | |
| 				goto out;
 | |
| 
 | |
| 			tlv->type = htons(CMD_ACK);
 | |
| 			tlv->len = htons(sizeof(*tlv));
 | |
| 			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
 | |
| 			if (err < 0)
 | |
| 				goto end_thread;
 | |
| 			break;
 | |
| 		}
 | |
| 		case CMD_STOP:
 | |
| 			if (state != CMD_START)
 | |
| 				break;
 | |
| 
 | |
| 			state = CMD_STOP;
 | |
| 
 | |
| 			exiting = true;
 | |
| 			bpf_xdp_detach(env.ifindex, flags, NULL);
 | |
| 
 | |
| 			tlv->type = htons(CMD_ACK);
 | |
| 			tlv->len = htons(sizeof(*tlv));
 | |
| 			err = send(ctrl_sockfd, buf, sizeof(*tlv), 0);
 | |
| 			goto end_thread;
 | |
| 		case CMD_GET_XDP_CAP: {
 | |
| 			LIBBPF_OPTS(bpf_xdp_query_opts, opts);
 | |
| 			unsigned long long val;
 | |
| 			size_t n;
 | |
| 
 | |
| 			err = bpf_xdp_query(env.ifindex, XDP_FLAGS_DRV_MODE,
 | |
| 					    &opts);
 | |
| 			if (err) {
 | |
| 				fprintf(stderr,
 | |
| 					"Failed querying XDP cap for device %s\n",
 | |
| 					env.ifname);
 | |
| 				goto end_thread;
 | |
| 			}
 | |
| 
 | |
| 			tlv->type = htons(CMD_ACK);
 | |
| 			n = sizeof(*tlv) + sizeof(opts.feature_flags);
 | |
| 			tlv->len = htons(n);
 | |
| 
 | |
| 			val = htobe64(opts.feature_flags);
 | |
| 			memcpy(tlv->data, &val, sizeof(val));
 | |
| 
 | |
| 			err = send(ctrl_sockfd, buf, n, 0);
 | |
| 			if (err < 0)
 | |
| 				goto end_thread;
 | |
| 			break;
 | |
| 		}
 | |
| 		case CMD_GET_STATS: {
 | |
| 			unsigned int key = 0, val;
 | |
| 			size_t n;
 | |
| 
 | |
| 			err = bpf_map__lookup_elem(skel->maps.dut_stats,
 | |
| 						   &key, sizeof(key),
 | |
| 						   &val, sizeof(val), 0);
 | |
| 			if (err) {
 | |
| 				fprintf(stderr,
 | |
| 					"bpf_map_lookup_elem failed (%d)\n", err);
 | |
| 				goto end_thread;
 | |
| 			}
 | |
| 
 | |
| 			tlv->type = htons(CMD_ACK);
 | |
| 			n = sizeof(*tlv) + sizeof(val);
 | |
| 			tlv->len = htons(n);
 | |
| 
 | |
| 			val = htonl(val);
 | |
| 			memcpy(tlv->data, &val, sizeof(val));
 | |
| 
 | |
| 			err = send(ctrl_sockfd, buf, n, 0);
 | |
| 			if (err < 0)
 | |
| 				goto end_thread;
 | |
| 			break;
 | |
| 		}
 | |
| 		default:
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| end_thread:
 | |
| 	pthread_join(dut_thread, NULL);
 | |
| out:
 | |
| 	bpf_xdp_detach(env.ifindex, flags, NULL);
 | |
| 	close(ctrl_sockfd);
 | |
| 	free_fds(sockfd, 1);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static bool tester_collect_detected_cap(struct xdp_features *skel,
 | |
| 					unsigned int dut_stats)
 | |
| {
 | |
| 	unsigned int err, key = 0, val;
 | |
| 
 | |
| 	if (!dut_stats)
 | |
| 		return false;
 | |
| 
 | |
| 	err = bpf_map__lookup_elem(skel->maps.stats, &key, sizeof(key),
 | |
| 				   &val, sizeof(val), 0);
 | |
| 	if (err) {
 | |
| 		fprintf(stderr, "bpf_map_lookup_elem failed (%d)\n", err);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	switch (env.feature.action) {
 | |
| 	case XDP_PASS:
 | |
| 	case XDP_TX:
 | |
| 	case XDP_REDIRECT:
 | |
| 		return val > 0;
 | |
| 	case XDP_DROP:
 | |
| 	case XDP_ABORTED:
 | |
| 		return val == 0;
 | |
| 	default:
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT)
 | |
| 		return val > 0;
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| static int send_and_recv_msg(int sockfd, enum test_commands cmd, void *val,
 | |
| 			     size_t val_size)
 | |
| {
 | |
| 	unsigned char buf[BUFSIZE] = {};
 | |
| 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
 | |
| 	int err;
 | |
| 
 | |
| 	tlv->type = htons(cmd);
 | |
| 	tlv->len = htons(sizeof(*tlv));
 | |
| 
 | |
| 	err = send(sockfd, buf, sizeof(*tlv), 0);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	err = recv_msg(sockfd, buf, BUFSIZE, val, val_size);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	return ntohs(tlv->type) == CMD_ACK ? 0 : -EINVAL;
 | |
| }
 | |
| 
 | |
| static int send_echo_msg(void)
 | |
| {
 | |
| 	unsigned char buf[sizeof(struct tlv_hdr)];
 | |
| 	struct tlv_hdr *tlv = (struct tlv_hdr *)buf;
 | |
| 	int sockfd, n;
 | |
| 
 | |
| 	sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
 | |
| 	if (sockfd < 0) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed creating data UDP socket on device %s\n",
 | |
| 			env.ifname);
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	tlv->type = htons(CMD_ECHO);
 | |
| 	tlv->len = htons(sizeof(*tlv));
 | |
| 
 | |
| 	n = sendto(sockfd, buf, sizeof(*tlv), MSG_NOSIGNAL | MSG_CONFIRM,
 | |
| 		   (struct sockaddr *)&env.dut_addr, sizeof(env.dut_addr));
 | |
| 	close(sockfd);
 | |
| 
 | |
| 	return n == ntohs(tlv->len) ? 0 : -EINVAL;
 | |
| }
 | |
| 
 | |
| static int tester_run(struct xdp_features *skel)
 | |
| {
 | |
| 	int flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE;
 | |
| 	unsigned long long advertised_feature;
 | |
| 	struct bpf_program *prog;
 | |
| 	unsigned int stats;
 | |
| 	int i, err, sockfd;
 | |
| 	bool detected_cap;
 | |
| 
 | |
| 	sockfd = socket(AF_INET6, SOCK_STREAM, 0);
 | |
| 	if (sockfd < 0) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed creating tester service control socket\n");
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	if (settimeo(sockfd, 1000) < 0)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	err = connect(sockfd, (struct sockaddr *)&env.dut_ctrl_addr,
 | |
| 		      sizeof(env.dut_ctrl_addr));
 | |
| 	if (err) {
 | |
| 		fprintf(stderr,
 | |
| 			"Failed connecting to the Device Under Test control socket\n");
 | |
| 		return -errno;
 | |
| 	}
 | |
| 
 | |
| 	err = send_and_recv_msg(sockfd, CMD_GET_XDP_CAP, &advertised_feature,
 | |
| 				sizeof(advertised_feature));
 | |
| 	if (err < 0) {
 | |
| 		close(sockfd);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	advertised_feature = be64toh(advertised_feature);
 | |
| 
 | |
| 	if (env.feature.drv_feature == NETDEV_XDP_ACT_NDO_XMIT ||
 | |
| 	    env.feature.action == XDP_TX)
 | |
| 		prog = skel->progs.xdp_tester_check_tx;
 | |
| 	else
 | |
| 		prog = skel->progs.xdp_tester_check_rx;
 | |
| 
 | |
| 	err = bpf_xdp_attach(env.ifindex, bpf_program__fd(prog), flags, NULL);
 | |
| 	if (err) {
 | |
| 		fprintf(stderr, "Failed attaching XDP program to device %s\n",
 | |
| 			env.ifname);
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	err = send_and_recv_msg(sockfd, CMD_START, NULL, 0);
 | |
| 	if (err)
 | |
| 		goto out;
 | |
| 
 | |
| 	for (i = 0; i < 10 && !exiting; i++) {
 | |
| 		err = send_echo_msg();
 | |
| 		if (err < 0)
 | |
| 			goto out;
 | |
| 
 | |
| 		sleep(1);
 | |
| 	}
 | |
| 
 | |
| 	err = send_and_recv_msg(sockfd, CMD_GET_STATS, &stats, sizeof(stats));
 | |
| 	if (err)
 | |
| 		goto out;
 | |
| 
 | |
| 	/* stop the test */
 | |
| 	err = send_and_recv_msg(sockfd, CMD_STOP, NULL, 0);
 | |
| 	/* send a new echo message to wake echo thread of the dut */
 | |
| 	send_echo_msg();
 | |
| 
 | |
| 	detected_cap = tester_collect_detected_cap(skel, ntohl(stats));
 | |
| 
 | |
| 	fprintf(stdout, "Feature %s: [%s][%s]\n", get_xdp_feature_str(),
 | |
| 		detected_cap ? GREEN("DETECTED") : RED("NOT DETECTED"),
 | |
| 		env.feature.drv_feature & advertised_feature ? GREEN("ADVERTISED")
 | |
| 							     : RED("NOT ADVERTISED"));
 | |
| out:
 | |
| 	bpf_xdp_detach(env.ifindex, flags, NULL);
 | |
| 	close(sockfd);
 | |
| 	return err < 0 ? err : 0;
 | |
| }
 | |
| 
 | |
| int main(int argc, char **argv)
 | |
| {
 | |
| 	struct xdp_features *skel;
 | |
| 	int err;
 | |
| 
 | |
| 	libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
 | |
| 	libbpf_set_print(libbpf_print_fn);
 | |
| 
 | |
| 	signal(SIGINT, sig_handler);
 | |
| 	signal(SIGTERM, sig_handler);
 | |
| 
 | |
| 	set_env_default();
 | |
| 
 | |
| 	/* Parse command line arguments */
 | |
| 	err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	if (env.ifindex < 0) {
 | |
| 		fprintf(stderr, "Invalid device name %s\n", env.ifname);
 | |
| 		return -ENODEV;
 | |
| 	}
 | |
| 
 | |
| 	/* Load and verify BPF application */
 | |
| 	skel = xdp_features__open();
 | |
| 	if (!skel) {
 | |
| 		fprintf(stderr, "Failed to open and load BPF skeleton\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	skel->rodata->tester_addr =
 | |
| 		((struct sockaddr_in6 *)&env.tester_addr)->sin6_addr;
 | |
| 	skel->rodata->dut_addr =
 | |
| 		((struct sockaddr_in6 *)&env.dut_addr)->sin6_addr;
 | |
| 
 | |
| 	/* Load & verify BPF programs */
 | |
| 	err = xdp_features__load(skel);
 | |
| 	if (err) {
 | |
| 		fprintf(stderr, "Failed to load and verify BPF skeleton\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	err = xdp_features__attach(skel);
 | |
| 	if (err) {
 | |
| 		fprintf(stderr, "Failed to attach BPF skeleton\n");
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	if (env.is_tester) {
 | |
| 		/* Tester */
 | |
| 		fprintf(stdout, "Starting tester service on device %s\n",
 | |
| 			env.ifname);
 | |
| 		err = tester_run(skel);
 | |
| 	} else {
 | |
| 		/* DUT */
 | |
| 		fprintf(stdout, "Starting test on device %s\n", env.ifname);
 | |
| 		err = dut_run(skel);
 | |
| 	}
 | |
| 
 | |
| cleanup:
 | |
| 	xdp_features__destroy(skel);
 | |
| 
 | |
| 	return err < 0 ? -err : 0;
 | |
| }
 |