719 lines · c
1// SPDX-License-Identifier: GPL-2.02#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 12841 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 else613 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}719