|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +#include <test_progs.h> |
| 3 | +#include <network_helpers.h> |
| 4 | +#include <net/if.h> |
| 5 | +#include <linux/if_ether.h> |
| 6 | +#include <linux/if_packet.h> |
| 7 | +#include <linux/ipv6.h> |
| 8 | +#include <linux/in6.h> |
| 9 | +#include <linux/udp.h> |
| 10 | +#include <bpf/bpf_endian.h> |
| 11 | +#include "test_xdp_do_redirect.skel.h" |
| 12 | + |
| 13 | +#define SYS(fmt, ...) \ |
| 14 | + ({ \ |
| 15 | + char cmd[1024]; \ |
| 16 | + snprintf(cmd, sizeof(cmd), fmt, ##__VA_ARGS__); \ |
| 17 | + if (!ASSERT_OK(system(cmd), cmd)) \ |
| 18 | + goto out; \ |
| 19 | + }) |
| 20 | + |
| 21 | +struct udp_packet { |
| 22 | + struct ethhdr eth; |
| 23 | + struct ipv6hdr iph; |
| 24 | + struct udphdr udp; |
| 25 | + __u8 payload[64 - sizeof(struct udphdr) |
| 26 | + - sizeof(struct ethhdr) - sizeof(struct ipv6hdr)]; |
| 27 | +} __packed; |
| 28 | + |
| 29 | +static struct udp_packet pkt_udp = { |
| 30 | + .eth.h_proto = __bpf_constant_htons(ETH_P_IPV6), |
| 31 | + .eth.h_dest = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}, |
| 32 | + .eth.h_source = {0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb}, |
| 33 | + .iph.version = 6, |
| 34 | + .iph.nexthdr = IPPROTO_UDP, |
| 35 | + .iph.payload_len = bpf_htons(sizeof(struct udp_packet) |
| 36 | + - offsetof(struct udp_packet, udp)), |
| 37 | + .iph.hop_limit = 2, |
| 38 | + .iph.saddr.s6_addr16 = {bpf_htons(0xfc00), 0, 0, 0, 0, 0, 0, bpf_htons(1)}, |
| 39 | + .iph.daddr.s6_addr16 = {bpf_htons(0xfc00), 0, 0, 0, 0, 0, 0, bpf_htons(2)}, |
| 40 | + .udp.source = bpf_htons(1), |
| 41 | + .udp.dest = bpf_htons(1), |
| 42 | + .udp.len = bpf_htons(sizeof(struct udp_packet) |
| 43 | + - offsetof(struct udp_packet, udp)), |
| 44 | + .payload = {0x42}, /* receiver XDP program matches on this */ |
| 45 | +}; |
| 46 | + |
| 47 | +static int attach_tc_prog(struct bpf_tc_hook *hook, int fd) |
| 48 | +{ |
| 49 | + DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, .prog_fd = fd); |
| 50 | + int ret; |
| 51 | + |
| 52 | + ret = bpf_tc_hook_create(hook); |
| 53 | + if (!ASSERT_OK(ret, "create tc hook")) |
| 54 | + return ret; |
| 55 | + |
| 56 | + ret = bpf_tc_attach(hook, &opts); |
| 57 | + if (!ASSERT_OK(ret, "bpf_tc_attach")) { |
| 58 | + bpf_tc_hook_destroy(hook); |
| 59 | + return ret; |
| 60 | + } |
| 61 | + |
| 62 | + return 0; |
| 63 | +} |
| 64 | + |
| 65 | +#define NUM_PKTS 10000 |
| 66 | +void test_xdp_do_redirect(void) |
| 67 | +{ |
| 68 | + int err, xdp_prog_fd, tc_prog_fd, ifindex_src, ifindex_dst; |
| 69 | + char data[sizeof(pkt_udp) + sizeof(__u32)]; |
| 70 | + struct test_xdp_do_redirect *skel = NULL; |
| 71 | + struct nstoken *nstoken = NULL; |
| 72 | + struct bpf_link *link; |
| 73 | + |
| 74 | + struct xdp_md ctx_in = { .data = sizeof(__u32), |
| 75 | + .data_end = sizeof(data) }; |
| 76 | + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, |
| 77 | + .data_in = &data, |
| 78 | + .data_size_in = sizeof(data), |
| 79 | + .ctx_in = &ctx_in, |
| 80 | + .ctx_size_in = sizeof(ctx_in), |
| 81 | + .flags = BPF_F_TEST_XDP_LIVE_FRAMES, |
| 82 | + .repeat = NUM_PKTS, |
| 83 | + .batch_size = 64, |
| 84 | + ); |
| 85 | + DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook, |
| 86 | + .attach_point = BPF_TC_INGRESS); |
| 87 | + |
| 88 | + memcpy(&data[sizeof(__u32)], &pkt_udp, sizeof(pkt_udp)); |
| 89 | + *((__u32 *)data) = 0x42; /* metadata test value */ |
| 90 | + |
| 91 | + skel = test_xdp_do_redirect__open(); |
| 92 | + if (!ASSERT_OK_PTR(skel, "skel")) |
| 93 | + return; |
| 94 | + |
| 95 | + /* The XDP program we run with bpf_prog_run() will cycle through all |
| 96 | + * three xmit (PASS/TX/REDIRECT) return codes starting from above, and |
| 97 | + * ending up with PASS, so we should end up with two packets on the dst |
| 98 | + * iface and NUM_PKTS-2 in the TC hook. We match the packets on the UDP |
| 99 | + * payload. |
| 100 | + */ |
| 101 | + SYS("ip netns add testns"); |
| 102 | + nstoken = open_netns("testns"); |
| 103 | + if (!ASSERT_OK_PTR(nstoken, "setns")) |
| 104 | + goto out; |
| 105 | + |
| 106 | + SYS("ip link add veth_src type veth peer name veth_dst"); |
| 107 | + SYS("ip link set dev veth_src address 00:11:22:33:44:55"); |
| 108 | + SYS("ip link set dev veth_dst address 66:77:88:99:aa:bb"); |
| 109 | + SYS("ip link set dev veth_src up"); |
| 110 | + SYS("ip link set dev veth_dst up"); |
| 111 | + SYS("ip addr add dev veth_src fc00::1/64"); |
| 112 | + SYS("ip addr add dev veth_dst fc00::2/64"); |
| 113 | + SYS("ip neigh add fc00::2 dev veth_src lladdr 66:77:88:99:aa:bb"); |
| 114 | + |
| 115 | + /* We enable forwarding in the test namespace because that will cause |
| 116 | + * the packets that go through the kernel stack (with XDP_PASS) to be |
| 117 | + * forwarded back out the same interface (because of the packet dst |
| 118 | + * combined with the interface addresses). When this happens, the |
| 119 | + * regular forwarding path will end up going through the same |
| 120 | + * veth_xdp_xmit() call as the XDP_REDIRECT code, which can cause a |
| 121 | + * deadlock if it happens on the same CPU. There's a local_bh_disable() |
| 122 | + * in the test_run code to prevent this, but an earlier version of the |
| 123 | + * code didn't have this, so we keep the test behaviour to make sure the |
| 124 | + * bug doesn't resurface. |
| 125 | + */ |
| 126 | + SYS("sysctl -qw net.ipv6.conf.all.forwarding=1"); |
| 127 | + |
| 128 | + ifindex_src = if_nametoindex("veth_src"); |
| 129 | + ifindex_dst = if_nametoindex("veth_dst"); |
| 130 | + if (!ASSERT_NEQ(ifindex_src, 0, "ifindex_src") || |
| 131 | + !ASSERT_NEQ(ifindex_dst, 0, "ifindex_dst")) |
| 132 | + goto out; |
| 133 | + |
| 134 | + memcpy(skel->rodata->expect_dst, &pkt_udp.eth.h_dest, ETH_ALEN); |
| 135 | + skel->rodata->ifindex_out = ifindex_src; /* redirect back to the same iface */ |
| 136 | + skel->rodata->ifindex_in = ifindex_src; |
| 137 | + ctx_in.ingress_ifindex = ifindex_src; |
| 138 | + tc_hook.ifindex = ifindex_src; |
| 139 | + |
| 140 | + if (!ASSERT_OK(test_xdp_do_redirect__load(skel), "load")) |
| 141 | + goto out; |
| 142 | + |
| 143 | + link = bpf_program__attach_xdp(skel->progs.xdp_count_pkts, ifindex_dst); |
| 144 | + if (!ASSERT_OK_PTR(link, "prog_attach")) |
| 145 | + goto out; |
| 146 | + skel->links.xdp_count_pkts = link; |
| 147 | + |
| 148 | + tc_prog_fd = bpf_program__fd(skel->progs.tc_count_pkts); |
| 149 | + if (attach_tc_prog(&tc_hook, tc_prog_fd)) |
| 150 | + goto out; |
| 151 | + |
| 152 | + xdp_prog_fd = bpf_program__fd(skel->progs.xdp_redirect); |
| 153 | + err = bpf_prog_test_run_opts(xdp_prog_fd, &opts); |
| 154 | + if (!ASSERT_OK(err, "prog_run")) |
| 155 | + goto out_tc; |
| 156 | + |
| 157 | + /* wait for the packets to be flushed */ |
| 158 | + kern_sync_rcu(); |
| 159 | + |
| 160 | + /* There will be one packet sent through XDP_REDIRECT and one through |
| 161 | + * XDP_TX; these will show up on the XDP counting program, while the |
| 162 | + * rest will be counted at the TC ingress hook (and the counting program |
| 163 | + * resets the packet payload so they don't get counted twice even though |
| 164 | + * they are re-xmited out the veth device |
| 165 | + */ |
| 166 | + ASSERT_EQ(skel->bss->pkts_seen_xdp, 2, "pkt_count_xdp"); |
| 167 | + ASSERT_EQ(skel->bss->pkts_seen_zero, 2, "pkt_count_zero"); |
| 168 | + ASSERT_EQ(skel->bss->pkts_seen_tc, NUM_PKTS - 2, "pkt_count_tc"); |
| 169 | + |
| 170 | +out_tc: |
| 171 | + bpf_tc_hook_destroy(&tc_hook); |
| 172 | +out: |
| 173 | + if (nstoken) |
| 174 | + close_netns(nstoken); |
| 175 | + system("ip netns del testns"); |
| 176 | + test_xdp_do_redirect__destroy(skel); |
| 177 | +} |
0 commit comments