1 | // SPDX-License-Identifier: GPL-2.0 |
2 | |
3 | /* This logic is lifted from a real-world use case of packet parsing, used in |
4 | * the open source library katran, a layer 4 load balancer. |
5 | * |
6 | * This test demonstrates how to parse packet contents using dynptrs. The |
7 | * original code (parsing without dynptrs) can be found in test_parse_tcp_hdr_opt.c |
8 | */ |
9 | |
10 | #include <linux/bpf.h> |
11 | #include <bpf/bpf_helpers.h> |
12 | #include <linux/tcp.h> |
13 | #include <stdbool.h> |
14 | #include <linux/ipv6.h> |
15 | #include <linux/if_ether.h> |
16 | #include "test_tcp_hdr_options.h" |
17 | #include "bpf_kfuncs.h" |
18 | |
19 | char _license[] SEC("license" ) = "GPL" ; |
20 | |
21 | /* Kind number used for experiments */ |
22 | const __u32 tcp_hdr_opt_kind_tpr = 0xFD; |
23 | /* Length of the tcp header option */ |
24 | const __u32 tcp_hdr_opt_len_tpr = 6; |
25 | /* maximum number of header options to check to lookup server_id */ |
26 | const __u32 tcp_hdr_opt_max_opt_checks = 15; |
27 | |
28 | __u32 server_id; |
29 | |
30 | static int parse_hdr_opt(struct bpf_dynptr *ptr, __u32 *off, __u8 *hdr_bytes_remaining, |
31 | __u32 *server_id) |
32 | { |
33 | __u8 kind, hdr_len; |
34 | __u8 buffer[sizeof(kind) + sizeof(hdr_len) + sizeof(*server_id)]; |
35 | __u8 *data; |
36 | |
37 | __builtin_memset(buffer, 0, sizeof(buffer)); |
38 | |
39 | data = bpf_dynptr_slice(ptr, *off, buffer, sizeof(buffer)); |
40 | if (!data) |
41 | return -1; |
42 | |
43 | kind = data[0]; |
44 | |
45 | if (kind == TCPOPT_EOL) |
46 | return -1; |
47 | |
48 | if (kind == TCPOPT_NOP) { |
49 | *off += 1; |
50 | *hdr_bytes_remaining -= 1; |
51 | return 0; |
52 | } |
53 | |
54 | if (*hdr_bytes_remaining < 2) |
55 | return -1; |
56 | |
57 | hdr_len = data[1]; |
58 | if (hdr_len > *hdr_bytes_remaining) |
59 | return -1; |
60 | |
61 | if (kind == tcp_hdr_opt_kind_tpr) { |
62 | if (hdr_len != tcp_hdr_opt_len_tpr) |
63 | return -1; |
64 | |
65 | __builtin_memcpy(server_id, (__u32 *)(data + 2), sizeof(*server_id)); |
66 | return 1; |
67 | } |
68 | |
69 | *off += hdr_len; |
70 | *hdr_bytes_remaining -= hdr_len; |
71 | return 0; |
72 | } |
73 | |
74 | SEC("xdp" ) |
75 | int xdp_ingress_v6(struct xdp_md *xdp) |
76 | { |
77 | __u8 buffer[sizeof(struct tcphdr)] = {}; |
78 | __u8 hdr_bytes_remaining; |
79 | struct tcphdr *tcp_hdr; |
80 | __u8 tcp_hdr_opt_len; |
81 | int err = 0; |
82 | __u32 off; |
83 | |
84 | struct bpf_dynptr ptr; |
85 | |
86 | bpf_dynptr_from_xdp(xdp, 0, &ptr); |
87 | |
88 | off = sizeof(struct ethhdr) + sizeof(struct ipv6hdr); |
89 | |
90 | tcp_hdr = bpf_dynptr_slice(&ptr, off, buffer, sizeof(buffer)); |
91 | if (!tcp_hdr) |
92 | return XDP_DROP; |
93 | |
94 | tcp_hdr_opt_len = (tcp_hdr->doff * 4) - sizeof(struct tcphdr); |
95 | if (tcp_hdr_opt_len < tcp_hdr_opt_len_tpr) |
96 | return XDP_DROP; |
97 | |
98 | hdr_bytes_remaining = tcp_hdr_opt_len; |
99 | |
100 | off += sizeof(struct tcphdr); |
101 | |
102 | /* max number of bytes of options in tcp header is 40 bytes */ |
103 | for (int i = 0; i < tcp_hdr_opt_max_opt_checks; i++) { |
104 | err = parse_hdr_opt(ptr: &ptr, off: &off, hdr_bytes_remaining: &hdr_bytes_remaining, server_id: &server_id); |
105 | |
106 | if (err || !hdr_bytes_remaining) |
107 | break; |
108 | } |
109 | |
110 | if (!server_id) |
111 | return XDP_DROP; |
112 | |
113 | return XDP_PASS; |
114 | } |
115 | |