Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Add selftests for raw syncookie helpers in TC mode

This commit extends selftests for the new BPF helpers
bpf_tcp_raw_{gen,check}_syncookie_ipv{4,6} to also test the TC BPF
functionality added in the previous commit.

Signed-off-by: Maxim Mikityanskiy <maximmi@nvidia.com>
Reviewed-by: Tariq Toukan <tariqt@nvidia.com>
Link: https://lore.kernel.org/r/20220615134847.3753567-7-maximmi@nvidia.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>

authored by

Maxim Mikityanskiy and committed by
Alexei Starovoitov
784d5dc0 9a4cf073

+244 -89
+46 -9
tools/testing/selftests/bpf/prog_tests/xdp_synproxy.c
··· 1 1 // SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause 2 2 /* Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ 3 3 4 + #define _GNU_SOURCE 4 5 #include <test_progs.h> 5 6 #include <network_helpers.h> 6 7 #include <ctype.h> ··· 13 12 goto out; \ 14 13 }) 15 14 16 - #define SYS_OUT(cmd) ({ \ 17 - FILE *f = popen((cmd), "r"); \ 18 - if (!ASSERT_OK_PTR(f, (cmd))) \ 15 + #define SYS_OUT(cmd, ...) ({ \ 16 + char buf[1024]; \ 17 + snprintf(buf, sizeof(buf), (cmd), ##__VA_ARGS__); \ 18 + FILE *f = popen(buf, "r"); \ 19 + if (!ASSERT_OK_PTR(f, buf)) \ 19 20 goto out; \ 20 21 f; \ 21 22 }) ··· 60 57 return ok; 61 58 } 62 59 63 - void test_xdp_synproxy(void) 60 + static void test_synproxy(bool xdp) 64 61 { 65 62 int server_fd = -1, client_fd = -1, accept_fd = -1; 63 + char *prog_id, *prog_id_end; 66 64 struct nstoken *ns = NULL; 67 65 FILE *ctrl_file = NULL; 68 66 char buf[CMD_OUT_BUF_SIZE]; ··· 80 76 * checksums and drops packets. 81 77 */ 82 78 SYS("ethtool -K tmp0 tx off"); 83 - /* Workaround required for veth. */ 84 - SYS("ip link set tmp0 xdp object xdp_dummy.o section xdp 2> /dev/null"); 79 + if (xdp) 80 + /* Workaround required for veth. */ 81 + SYS("ip link set tmp0 xdp object xdp_dummy.o section xdp 2> /dev/null"); 85 82 86 83 ns = open_netns("synproxy"); 87 84 if (!ASSERT_OK_PTR(ns, "setns")) ··· 102 97 SYS("iptables -t filter -A INPUT \ 103 98 -i tmp1 -m state --state INVALID -j DROP"); 104 99 105 - ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --ports 8080 --single \ 106 - --mss4 1460 --mss6 1440 --wscale 7 --ttl 64"); 100 + ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --ports 8080 \ 101 + --single --mss4 1460 --mss6 1440 \ 102 + --wscale 7 --ttl 64%s", xdp ? "" : " --tc"); 107 103 size = fread(buf, 1, sizeof(buf), ctrl_file); 108 104 pclose(ctrl_file); 109 105 if (!expect_str(buf, size, "Total SYNACKs generated: 0\n", 110 106 "initial SYNACKs")) 111 107 goto out; 108 + 109 + if (!xdp) { 110 + ctrl_file = SYS_OUT("tc filter show dev tmp1 ingress"); 111 + size = fread(buf, 1, sizeof(buf), ctrl_file); 112 + pclose(ctrl_file); 113 + prog_id = memmem(buf, size, " id ", 4); 114 + if (!ASSERT_OK_PTR(prog_id, "find prog id")) 115 + goto out; 116 + prog_id += 4; 117 + if (!ASSERT_LT(prog_id, buf + size, "find prog id begin")) 118 + goto out; 119 + prog_id_end = prog_id; 120 + while (prog_id_end < buf + size && *prog_id_end >= '0' && 121 + *prog_id_end <= '9') 122 + prog_id_end++; 123 + if (!ASSERT_LT(prog_id_end, buf + size, "find prog id end")) 124 + goto out; 125 + *prog_id_end = '\0'; 126 + } 112 127 113 128 server_fd = start_server(AF_INET, SOCK_STREAM, "198.18.0.2", 8080, 0); 114 129 if (!ASSERT_GE(server_fd, 0, "start_server")) ··· 149 124 if (!ASSERT_OK_PTR(ns, "setns")) 150 125 goto out; 151 126 152 - ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --single"); 127 + if (xdp) 128 + ctrl_file = SYS_OUT("./xdp_synproxy --iface tmp1 --single"); 129 + else 130 + ctrl_file = SYS_OUT("./xdp_synproxy --prog %s --single", 131 + prog_id); 153 132 size = fread(buf, 1, sizeof(buf), ctrl_file); 154 133 pclose(ctrl_file); 155 134 if (!expect_str(buf, size, "Total SYNACKs generated: 1\n", ··· 172 143 173 144 system("ip link del tmp0"); 174 145 system("ip netns del synproxy"); 146 + } 147 + 148 + void test_xdp_synproxy(void) 149 + { 150 + if (test__start_subtest("xdp")) 151 + test_synproxy(true); 152 + if (test__start_subtest("tc")) 153 + test_synproxy(false); 175 154 }
+126 -56
tools/testing/selftests/bpf/progs/xdp_synproxy_kern.c
··· 7 7 #include <bpf/bpf_endian.h> 8 8 #include <asm/errno.h> 9 9 10 + #define TC_ACT_OK 0 11 + #define TC_ACT_SHOT 2 12 + 10 13 #define NSEC_PER_SEC 1000000000L 11 14 12 15 #define ETH_ALEN 6 ··· 82 79 __u32 len_tuple, 83 80 struct bpf_ct_opts *opts, 84 81 __u32 len_opts) __ksym; 82 + 83 + extern struct nf_conn *bpf_skb_ct_lookup(struct __sk_buff *skb_ctx, 84 + struct bpf_sock_tuple *bpf_tuple, 85 + u32 len_tuple, 86 + struct bpf_ct_opts *opts, 87 + u32 len_opts) __ksym; 85 88 86 89 extern void bpf_ct_release(struct nf_conn *ct) __ksym; 87 90 ··· 391 382 return XDP_TX; 392 383 } 393 384 394 - static __always_inline int tcp_lookup(struct xdp_md *ctx, struct header_pointers *hdr) 385 + static __always_inline int tcp_lookup(void *ctx, struct header_pointers *hdr, bool xdp) 395 386 { 396 387 struct bpf_ct_opts ct_lookup_opts = { 397 388 .netns_id = BPF_F_CURRENT_NETNS, ··· 425 416 */ 426 417 return XDP_ABORTED; 427 418 } 428 - ct = bpf_xdp_ct_lookup(ctx, &tup, tup_size, &ct_lookup_opts, sizeof(ct_lookup_opts)); 419 + if (xdp) 420 + ct = bpf_xdp_ct_lookup(ctx, &tup, tup_size, &ct_lookup_opts, sizeof(ct_lookup_opts)); 421 + else 422 + ct = bpf_skb_ct_lookup(ctx, &tup, tup_size, &ct_lookup_opts, sizeof(ct_lookup_opts)); 429 423 if (ct) { 430 424 unsigned long status = ct->status; 431 425 ··· 541 529 } 542 530 543 531 static __always_inline int syncookie_handle_syn(struct header_pointers *hdr, 544 - struct xdp_md *ctx, 545 - void *data, void *data_end) 532 + void *ctx, 533 + void *data, void *data_end, 534 + bool xdp) 546 535 { 547 536 __u32 old_pkt_size, new_pkt_size; 548 537 /* Unlike clang 10, clang 11 and 12 generate code that doesn't pass the ··· 679 666 /* Set the new packet size. */ 680 667 old_pkt_size = data_end - data; 681 668 new_pkt_size = sizeof(*hdr->eth) + ip_len + hdr->tcp->doff * 4; 682 - if (bpf_xdp_adjust_tail(ctx, new_pkt_size - old_pkt_size)) 683 - return XDP_ABORTED; 669 + if (xdp) { 670 + if (bpf_xdp_adjust_tail(ctx, new_pkt_size - old_pkt_size)) 671 + return XDP_ABORTED; 672 + } else { 673 + if (bpf_skb_change_tail(ctx, new_pkt_size, 0)) 674 + return XDP_ABORTED; 675 + } 684 676 685 677 values_inc_synacks(); 686 678 ··· 711 693 return XDP_PASS; 712 694 } 713 695 696 + static __always_inline int syncookie_part1(void *ctx, void *data, void *data_end, 697 + struct header_pointers *hdr, bool xdp) 698 + { 699 + struct bpf_ct_opts ct_lookup_opts = { 700 + .netns_id = BPF_F_CURRENT_NETNS, 701 + .l4proto = IPPROTO_TCP, 702 + }; 703 + int ret; 704 + 705 + ret = tcp_dissect(data, data_end, hdr); 706 + if (ret != XDP_TX) 707 + return ret; 708 + 709 + ret = tcp_lookup(ctx, hdr, xdp); 710 + if (ret != XDP_TX) 711 + return ret; 712 + 713 + /* Packet is TCP and doesn't belong to an established connection. */ 714 + 715 + if ((hdr->tcp->syn ^ hdr->tcp->ack) != 1) 716 + return XDP_DROP; 717 + 718 + /* Grow the TCP header to TCP_MAXLEN to be able to pass any hdr->tcp_len 719 + * to bpf_tcp_raw_gen_syncookie_ipv{4,6} and pass the verifier. 720 + */ 721 + if (xdp) { 722 + if (bpf_xdp_adjust_tail(ctx, TCP_MAXLEN - hdr->tcp_len)) 723 + return XDP_ABORTED; 724 + } else { 725 + /* Without volatile the verifier throws this error: 726 + * R9 32-bit pointer arithmetic prohibited 727 + */ 728 + volatile u64 old_len = data_end - data; 729 + 730 + if (bpf_skb_change_tail(ctx, old_len + TCP_MAXLEN - hdr->tcp_len, 0)) 731 + return XDP_ABORTED; 732 + } 733 + 734 + return XDP_TX; 735 + } 736 + 737 + static __always_inline int syncookie_part2(void *ctx, void *data, void *data_end, 738 + struct header_pointers *hdr, bool xdp) 739 + { 740 + if (hdr->ipv4) { 741 + hdr->eth = data; 742 + hdr->ipv4 = (void *)hdr->eth + sizeof(*hdr->eth); 743 + /* IPV4_MAXLEN is needed when calculating checksum. 744 + * At least sizeof(struct iphdr) is needed here to access ihl. 745 + */ 746 + if ((void *)hdr->ipv4 + IPV4_MAXLEN > data_end) 747 + return XDP_ABORTED; 748 + hdr->tcp = (void *)hdr->ipv4 + hdr->ipv4->ihl * 4; 749 + } else if (hdr->ipv6) { 750 + hdr->eth = data; 751 + hdr->ipv6 = (void *)hdr->eth + sizeof(*hdr->eth); 752 + hdr->tcp = (void *)hdr->ipv6 + sizeof(*hdr->ipv6); 753 + } else { 754 + return XDP_ABORTED; 755 + } 756 + 757 + if ((void *)hdr->tcp + TCP_MAXLEN > data_end) 758 + return XDP_ABORTED; 759 + 760 + /* We run out of registers, tcp_len gets spilled to the stack, and the 761 + * verifier forgets its min and max values checked above in tcp_dissect. 762 + */ 763 + hdr->tcp_len = hdr->tcp->doff * 4; 764 + if (hdr->tcp_len < sizeof(*hdr->tcp)) 765 + return XDP_ABORTED; 766 + 767 + return hdr->tcp->syn ? syncookie_handle_syn(hdr, ctx, data, data_end, xdp) : 768 + syncookie_handle_ack(hdr); 769 + } 770 + 714 771 SEC("xdp") 715 772 int syncookie_xdp(struct xdp_md *ctx) 716 773 { 717 774 void *data_end = (void *)(long)ctx->data_end; 718 775 void *data = (void *)(long)ctx->data; 719 776 struct header_pointers hdr; 720 - __s64 value; 721 777 int ret; 722 778 723 - struct bpf_ct_opts ct_lookup_opts = { 724 - .netns_id = BPF_F_CURRENT_NETNS, 725 - .l4proto = IPPROTO_TCP, 726 - }; 727 - 728 - ret = tcp_dissect(data, data_end, &hdr); 779 + ret = syncookie_part1(ctx, data, data_end, &hdr, true); 729 780 if (ret != XDP_TX) 730 781 return ret; 731 - 732 - ret = tcp_lookup(ctx, &hdr); 733 - if (ret != XDP_TX) 734 - return ret; 735 - 736 - /* Packet is TCP and doesn't belong to an established connection. */ 737 - 738 - if ((hdr.tcp->syn ^ hdr.tcp->ack) != 1) 739 - return XDP_DROP; 740 - 741 - /* Grow the TCP header to TCP_MAXLEN to be able to pass any hdr.tcp_len 742 - * to bpf_tcp_raw_gen_syncookie_ipv{4,6} and pass the verifier. 743 - */ 744 - if (bpf_xdp_adjust_tail(ctx, TCP_MAXLEN - hdr.tcp_len)) 745 - return XDP_ABORTED; 746 782 747 783 data_end = (void *)(long)ctx->data_end; 748 784 data = (void *)(long)ctx->data; 749 785 750 - if (hdr.ipv4) { 751 - hdr.eth = data; 752 - hdr.ipv4 = (void *)hdr.eth + sizeof(*hdr.eth); 753 - /* IPV4_MAXLEN is needed when calculating checksum. 754 - * At least sizeof(struct iphdr) is needed here to access ihl. 755 - */ 756 - if ((void *)hdr.ipv4 + IPV4_MAXLEN > data_end) 757 - return XDP_ABORTED; 758 - hdr.tcp = (void *)hdr.ipv4 + hdr.ipv4->ihl * 4; 759 - } else if (hdr.ipv6) { 760 - hdr.eth = data; 761 - hdr.ipv6 = (void *)hdr.eth + sizeof(*hdr.eth); 762 - hdr.tcp = (void *)hdr.ipv6 + sizeof(*hdr.ipv6); 763 - } else { 764 - return XDP_ABORTED; 786 + return syncookie_part2(ctx, data, data_end, &hdr, true); 787 + } 788 + 789 + SEC("tc") 790 + int syncookie_tc(struct __sk_buff *skb) 791 + { 792 + void *data_end = (void *)(long)skb->data_end; 793 + void *data = (void *)(long)skb->data; 794 + struct header_pointers hdr; 795 + int ret; 796 + 797 + ret = syncookie_part1(skb, data, data_end, &hdr, false); 798 + if (ret != XDP_TX) 799 + return ret == XDP_PASS ? TC_ACT_OK : TC_ACT_SHOT; 800 + 801 + data_end = (void *)(long)skb->data_end; 802 + data = (void *)(long)skb->data; 803 + 804 + ret = syncookie_part2(skb, data, data_end, &hdr, false); 805 + switch (ret) { 806 + case XDP_PASS: 807 + return TC_ACT_OK; 808 + case XDP_TX: 809 + return bpf_redirect(skb->ifindex, 0); 810 + default: 811 + return TC_ACT_SHOT; 765 812 } 766 - 767 - if ((void *)hdr.tcp + TCP_MAXLEN > data_end) 768 - return XDP_ABORTED; 769 - 770 - /* We run out of registers, tcp_len gets spilled to the stack, and the 771 - * verifier forgets its min and max values checked above in tcp_dissect. 772 - */ 773 - hdr.tcp_len = hdr.tcp->doff * 4; 774 - if (hdr.tcp_len < sizeof(*hdr.tcp)) 775 - return XDP_ABORTED; 776 - 777 - return hdr.tcp->syn ? syncookie_handle_syn(&hdr, ctx, data, data_end) : 778 - syncookie_handle_ack(&hdr); 779 813 } 780 814 781 815 char _license[] SEC("license") = "GPL";
+72 -24
tools/testing/selftests/bpf/xdp_synproxy.c
··· 18 18 19 19 static unsigned int ifindex; 20 20 static __u32 attached_prog_id; 21 + static bool attached_tc; 21 22 22 23 static void noreturn cleanup(int sig) 23 24 { 24 - DECLARE_LIBBPF_OPTS(bpf_xdp_attach_opts, opts); 25 + LIBBPF_OPTS(bpf_xdp_attach_opts, opts); 25 26 int prog_fd; 26 27 int err; 27 28 28 29 if (attached_prog_id == 0) 29 30 exit(0); 31 + 32 + if (attached_tc) { 33 + LIBBPF_OPTS(bpf_tc_hook, hook, 34 + .ifindex = ifindex, 35 + .attach_point = BPF_TC_INGRESS); 36 + 37 + err = bpf_tc_hook_destroy(&hook); 38 + if (err < 0) { 39 + fprintf(stderr, "Error: bpf_tc_hook_destroy: %s\n", strerror(-err)); 40 + fprintf(stderr, "Failed to destroy the TC hook\n"); 41 + exit(1); 42 + } 43 + exit(0); 44 + } 30 45 31 46 prog_fd = bpf_prog_get_fd_by_id(attached_prog_id); 32 47 if (prog_fd < 0) { ··· 70 55 71 56 static noreturn void usage(const char *progname) 72 57 { 73 - fprintf(stderr, "Usage: %s [--iface <iface>|--prog <prog_id>] [--mss4 <mss ipv4> --mss6 <mss ipv6> --wscale <wscale> --ttl <ttl>] [--ports <port1>,<port2>,...] [--single]\n", 58 + fprintf(stderr, "Usage: %s [--iface <iface>|--prog <prog_id>] [--mss4 <mss ipv4> --mss6 <mss ipv6> --wscale <wscale> --ttl <ttl>] [--ports <port1>,<port2>,...] [--single] [--tc]\n", 74 59 progname); 75 60 exit(1); 76 61 } ··· 89 74 } 90 75 91 76 static void parse_options(int argc, char *argv[], unsigned int *ifindex, __u32 *prog_id, 92 - __u64 *tcpipopts, char **ports, bool *single) 77 + __u64 *tcpipopts, char **ports, bool *single, bool *tc) 93 78 { 94 79 static struct option long_options[] = { 95 80 { "help", no_argument, NULL, 'h' }, ··· 101 86 { "ttl", required_argument, NULL, 't' }, 102 87 { "ports", required_argument, NULL, 'p' }, 103 88 { "single", no_argument, NULL, 's' }, 89 + { "tc", no_argument, NULL, 'c' }, 104 90 { NULL, 0, NULL, 0 }, 105 91 }; 106 92 unsigned long mss4, mss6, wscale, ttl; ··· 159 143 case 's': 160 144 *single = true; 161 145 break; 146 + case 'c': 147 + *tc = true; 148 + break; 162 149 default: 163 150 usage(argv[0]); 164 151 } ··· 183 164 usage(argv[0]); 184 165 } 185 166 186 - static int syncookie_attach(const char *argv0, unsigned int ifindex) 167 + static int syncookie_attach(const char *argv0, unsigned int ifindex, bool tc) 187 168 { 188 169 struct bpf_prog_info info = {}; 189 170 __u32 info_len = sizeof(info); ··· 207 188 return err; 208 189 } 209 190 210 - prog = bpf_object__find_program_by_name(obj, "syncookie_xdp"); 191 + prog = bpf_object__find_program_by_name(obj, tc ? "syncookie_tc" : "syncookie_xdp"); 211 192 if (!prog) { 212 - fprintf(stderr, "Error: bpf_object__find_program_by_name: program syncookie_xdp was not found\n"); 193 + fprintf(stderr, "Error: bpf_object__find_program_by_name: program was not found\n"); 213 194 return -ENOENT; 214 195 } 215 196 ··· 220 201 fprintf(stderr, "Error: bpf_obj_get_info_by_fd: %s\n", strerror(-err)); 221 202 goto out; 222 203 } 204 + attached_tc = tc; 223 205 attached_prog_id = info.id; 224 206 signal(SIGINT, cleanup); 225 207 signal(SIGTERM, cleanup); 226 - err = bpf_xdp_attach(ifindex, prog_fd, XDP_FLAGS_UPDATE_IF_NOEXIST, NULL); 227 - if (err < 0) { 228 - fprintf(stderr, "Error: bpf_set_link_xdp_fd: %s\n", strerror(-err)); 229 - signal(SIGINT, SIG_DFL); 230 - signal(SIGTERM, SIG_DFL); 231 - attached_prog_id = 0; 232 - goto out; 208 + if (tc) { 209 + LIBBPF_OPTS(bpf_tc_hook, hook, 210 + .ifindex = ifindex, 211 + .attach_point = BPF_TC_INGRESS); 212 + LIBBPF_OPTS(bpf_tc_opts, opts, 213 + .handle = 1, 214 + .priority = 1, 215 + .prog_fd = prog_fd); 216 + 217 + err = bpf_tc_hook_create(&hook); 218 + if (err < 0) { 219 + fprintf(stderr, "Error: bpf_tc_hook_create: %s\n", 220 + strerror(-err)); 221 + goto fail; 222 + } 223 + err = bpf_tc_attach(&hook, &opts); 224 + if (err < 0) { 225 + fprintf(stderr, "Error: bpf_tc_attach: %s\n", 226 + strerror(-err)); 227 + goto fail; 228 + } 229 + 230 + } else { 231 + err = bpf_xdp_attach(ifindex, prog_fd, 232 + XDP_FLAGS_UPDATE_IF_NOEXIST, NULL); 233 + if (err < 0) { 234 + fprintf(stderr, "Error: bpf_set_link_xdp_fd: %s\n", 235 + strerror(-err)); 236 + goto fail; 237 + } 233 238 } 234 239 err = 0; 235 240 out: 236 241 bpf_object__close(obj); 237 242 return err; 243 + fail: 244 + signal(SIGINT, SIG_DFL); 245 + signal(SIGTERM, SIG_DFL); 246 + attached_prog_id = 0; 247 + goto out; 238 248 } 239 249 240 250 static int syncookie_open_bpf_maps(__u32 prog_id, int *values_map_fd, int *ports_map_fd) ··· 296 248 goto out; 297 249 } 298 250 299 - if (prog_info.type != BPF_PROG_TYPE_XDP) { 300 - fprintf(stderr, "Error: BPF prog type is not BPF_PROG_TYPE_XDP\n"); 301 - err = -ENOENT; 302 - goto out; 303 - } 304 251 if (prog_info.nr_map_ids < 2) { 305 252 fprintf(stderr, "Error: Found %u BPF maps, expected at least 2\n", 306 253 prog_info.nr_map_ids); ··· 362 319 char *ports; 363 320 bool single; 364 321 int err = 0; 322 + bool tc; 365 323 366 - parse_options(argc, argv, &ifindex, &prog_id, &tcpipopts, &ports, &single); 324 + parse_options(argc, argv, &ifindex, &prog_id, &tcpipopts, &ports, 325 + &single, &tc); 367 326 368 327 if (prog_id == 0) { 369 - err = bpf_xdp_query_id(ifindex, 0, &prog_id); 370 - if (err < 0) { 371 - fprintf(stderr, "Error: bpf_get_link_xdp_id: %s\n", strerror(-err)); 372 - goto out; 328 + if (!tc) { 329 + err = bpf_xdp_query_id(ifindex, 0, &prog_id); 330 + if (err < 0) { 331 + fprintf(stderr, "Error: bpf_get_link_xdp_id: %s\n", 332 + strerror(-err)); 333 + goto out; 334 + } 373 335 } 374 336 if (prog_id == 0) { 375 - err = syncookie_attach(argv[0], ifindex); 337 + err = syncookie_attach(argv[0], ifindex, tc); 376 338 if (err < 0) 377 339 goto out; 378 340 prog_id = attached_prog_id;