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