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