1 | // SPDX-License-Identifier: GPL-2.0 |
2 | #include <vmlinux.h> |
3 | #include <bpf/bpf_helpers.h> |
4 | |
5 | #define ETH_ALEN 6 |
6 | #define HDR_SZ (sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + sizeof(struct udphdr)) |
7 | |
8 | /** |
9 | * enum frame_mark - magics to distinguish page/packet paths |
10 | * @MARK_XMIT: page was recycled due to the frame being "xmitted" by the NIC. |
11 | * @MARK_IN: frame is being processed by the input XDP prog. |
12 | * @MARK_SKB: frame did hit the TC ingress hook as an skb. |
13 | */ |
14 | enum frame_mark { |
15 | MARK_XMIT = 0U, |
16 | MARK_IN = 0x42, |
17 | MARK_SKB = 0x45, |
18 | }; |
19 | |
20 | const volatile int ifindex_out; |
21 | const volatile int ifindex_in; |
22 | const volatile __u8 expect_dst[ETH_ALEN]; |
23 | volatile int pkts_seen_xdp = 0; |
24 | volatile int pkts_seen_zero = 0; |
25 | volatile int pkts_seen_tc = 0; |
26 | volatile int retcode = XDP_REDIRECT; |
27 | |
28 | SEC("xdp" ) |
29 | int xdp_redirect(struct xdp_md *xdp) |
30 | { |
31 | __u32 *metadata = (void *)(long)xdp->data_meta; |
32 | void *data_end = (void *)(long)xdp->data_end; |
33 | void *data = (void *)(long)xdp->data; |
34 | |
35 | __u8 *payload = data + HDR_SZ; |
36 | int ret = retcode; |
37 | |
38 | if (payload + 1 > data_end) |
39 | return XDP_ABORTED; |
40 | |
41 | if (xdp->ingress_ifindex != (__u32)ifindex_in) |
42 | return XDP_ABORTED; |
43 | |
44 | if (metadata + 1 > data) |
45 | return XDP_ABORTED; |
46 | |
47 | if (*metadata != 0x42) |
48 | return XDP_ABORTED; |
49 | |
50 | if (*payload == MARK_XMIT) |
51 | pkts_seen_zero++; |
52 | |
53 | *payload = MARK_IN; |
54 | |
55 | if (bpf_xdp_adjust_meta(xdp, sizeof(__u64))) |
56 | return XDP_ABORTED; |
57 | |
58 | if (retcode > XDP_PASS) |
59 | retcode--; |
60 | |
61 | if (ret == XDP_REDIRECT) |
62 | return bpf_redirect(ifindex_out, 0); |
63 | |
64 | return ret; |
65 | } |
66 | |
67 | static bool check_pkt(void *data, void *data_end, const __u32 mark) |
68 | { |
69 | struct ipv6hdr *iph = data + sizeof(struct ethhdr); |
70 | __u8 *payload = data + HDR_SZ; |
71 | |
72 | if (payload + 1 > data_end) |
73 | return false; |
74 | |
75 | if (iph->nexthdr != IPPROTO_UDP || *payload != MARK_IN) |
76 | return false; |
77 | |
78 | /* reset the payload so the same packet doesn't get counted twice when |
79 | * it cycles back through the kernel path and out the dst veth |
80 | */ |
81 | *payload = mark; |
82 | return true; |
83 | } |
84 | |
85 | SEC("xdp" ) |
86 | int xdp_count_pkts(struct xdp_md *xdp) |
87 | { |
88 | void *data = (void *)(long)xdp->data; |
89 | void *data_end = (void *)(long)xdp->data_end; |
90 | |
91 | if (check_pkt(data, data_end, MARK_XMIT)) |
92 | pkts_seen_xdp++; |
93 | |
94 | /* Return %XDP_DROP to recycle the data page with %MARK_XMIT, like |
95 | * it exited a physical NIC. Those pages will be counted in the |
96 | * pkts_seen_zero counter above. |
97 | */ |
98 | return XDP_DROP; |
99 | } |
100 | |
101 | SEC("tc" ) |
102 | int tc_count_pkts(struct __sk_buff *skb) |
103 | { |
104 | void *data = (void *)(long)skb->data; |
105 | void *data_end = (void *)(long)skb->data_end; |
106 | |
107 | if (check_pkt(data, data_end, MARK_SKB)) |
108 | pkts_seen_tc++; |
109 | |
110 | /* Will be either recycled or freed, %MARK_SKB makes sure it won't |
111 | * hit any of the counters above. |
112 | */ |
113 | return 0; |
114 | } |
115 | |
116 | char _license[] SEC("license" ) = "GPL" ; |
117 | |