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 
parse_hdr_opt(const struct xdp_md * xdp,struct hdr_opt_state * state)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")
xdp_ingress_v6(struct xdp_md * 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, &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