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