1 | // SPDX-License-Identifier: GPL-2.0 |
---|---|
2 | |
3 | /* This parsing logic is taken from the open source library katran, a layer 4 |
4 | * load balancer. |
5 | * |
6 | * This code logic using dynptrs can be found in test_parse_tcp_hdr_opt_dynptr.c |
7 | * |
8 | * https://github.com/facebookincubator/katran/blob/main/katran/lib/bpf/pckt_parsing.h |
9 | */ |
10 | |
11 | #include <linux/bpf.h> |
12 | #include <bpf/bpf_helpers.h> |
13 | #include <linux/tcp.h> |
14 | #include <stdbool.h> |
15 | #include <linux/ipv6.h> |
16 | #include <linux/if_ether.h> |
17 | #include "test_tcp_hdr_options.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 | struct hdr_opt_state { |
31 | __u32 server_id; |
32 | __u8 byte_offset; |
33 | __u8 hdr_bytes_remaining; |
34 | }; |
35 | |
36 | static int parse_hdr_opt(const struct xdp_md *xdp, struct hdr_opt_state *state) |
37 | { |
38 | const void *data = (void *)(long)xdp->data; |
39 | const void *data_end = (void *)(long)xdp->data_end; |
40 | __u8 *tcp_opt, kind, hdr_len; |
41 | |
42 | tcp_opt = (__u8 *)(data + state->byte_offset); |
43 | if (tcp_opt + 1 > data_end) |
44 | return -1; |
45 | |
46 | kind = tcp_opt[0]; |
47 | |
48 | if (kind == TCPOPT_EOL) |
49 | return -1; |
50 | |
51 | if (kind == TCPOPT_NOP) { |
52 | state->hdr_bytes_remaining--; |
53 | state->byte_offset++; |
54 | return 0; |
55 | } |
56 | |
57 | if (state->hdr_bytes_remaining < 2 || |
58 | tcp_opt + sizeof(__u8) + sizeof(__u8) > data_end) |
59 | return -1; |
60 | |
61 | hdr_len = tcp_opt[1]; |
62 | if (hdr_len > state->hdr_bytes_remaining) |
63 | return -1; |
64 | |
65 | if (kind == tcp_hdr_opt_kind_tpr) { |
66 | if (hdr_len != tcp_hdr_opt_len_tpr) |
67 | return -1; |
68 | |
69 | if (tcp_opt + tcp_hdr_opt_len_tpr > data_end) |
70 | return -1; |
71 | |
72 | state->server_id = *(__u32 *)&tcp_opt[2]; |
73 | return 1; |
74 | } |
75 | |
76 | state->hdr_bytes_remaining -= hdr_len; |
77 | state->byte_offset += hdr_len; |
78 | return 0; |
79 | } |
80 | |
81 | SEC("xdp") |
82 | int xdp_ingress_v6(struct xdp_md *xdp) |
83 | { |
84 | const void *data = (void *)(long)xdp->data; |
85 | const void *data_end = (void *)(long)xdp->data_end; |
86 | struct hdr_opt_state opt_state = {}; |
87 | __u8 tcp_hdr_opt_len = 0; |
88 | struct tcphdr *tcp_hdr; |
89 | __u64 tcp_offset = 0; |
90 | int err; |
91 | |
92 | tcp_offset = sizeof(struct ethhdr) + sizeof(struct ipv6hdr); |
93 | tcp_hdr = (struct tcphdr *)(data + tcp_offset); |
94 | if (tcp_hdr + 1 > data_end) |
95 | return XDP_DROP; |
96 | |
97 | tcp_hdr_opt_len = (tcp_hdr->doff * 4) - sizeof(struct tcphdr); |
98 | if (tcp_hdr_opt_len < tcp_hdr_opt_len_tpr) |
99 | return XDP_DROP; |
100 | |
101 | opt_state.hdr_bytes_remaining = tcp_hdr_opt_len; |
102 | opt_state.byte_offset = sizeof(struct tcphdr) + tcp_offset; |
103 | |
104 | /* max number of bytes of options in tcp header is 40 bytes */ |
105 | for (int i = 0; i < tcp_hdr_opt_max_opt_checks; i++) { |
106 | err = parse_hdr_opt(xdp, state: &opt_state); |
107 | |
108 | if (err || !opt_state.hdr_bytes_remaining) |
109 | break; |
110 | } |
111 | |
112 | if (!opt_state.server_id) |
113 | return XDP_DROP; |
114 | |
115 | server_id = opt_state.server_id; |
116 | |
117 | return XDP_PASS; |
118 | } |
119 |