xref: /linux/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c (revision 515186b7be488f37c63c2436fc2d1a160ef9bb95)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2020 Facebook */
3 
4 #include <stddef.h>
5 #include <errno.h>
6 #include <stdbool.h>
7 #include <sys/types.h>
8 #include <sys/socket.h>
9 #include <linux/ipv6.h>
10 #include <linux/tcp.h>
11 #include <linux/socket.h>
12 #include <linux/bpf.h>
13 #include <linux/types.h>
14 #include <bpf/bpf_helpers.h>
15 #include <bpf/bpf_endian.h>
16 #define BPF_PROG_TEST_TCP_HDR_OPTIONS
17 #include "test_tcp_hdr_options.h"
18 
19 __u16 last_addr16_n = __bpf_htons(1);
20 __u16 active_lport_n = 0;
21 __u16 active_lport_h = 0;
22 __u16 passive_lport_n = 0;
23 __u16 passive_lport_h = 0;
24 
25 /* options received at passive side */
26 unsigned int nr_pure_ack = 0;
27 unsigned int nr_data = 0;
28 unsigned int nr_syn = 0;
29 unsigned int nr_fin = 0;
30 unsigned int nr_hwtstamp = 0;
31 
32 bool nodelay_est_ok = false;
33 bool nodelay_hdr_len_reject = false;
34 bool nodelay_write_hdr_reject = false;
35 
36 /* Check the header received from the active side */
__check_active_hdr_in(struct bpf_sock_ops * skops,bool check_syn)37 static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn)
38 {
39 	union {
40 		struct tcphdr th;
41 		struct ipv6hdr ip6;
42 		struct tcp_exprm_opt exprm_opt;
43 		struct tcp_opt reg_opt;
44 		__u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */
45 	} hdr = {};
46 	__u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0;
47 	struct tcphdr *pth;
48 	int ret;
49 
50 	hdr.reg_opt.kind = 0xB9;
51 
52 	/* The option is 4 bytes long instead of 2 bytes */
53 	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags);
54 	if (ret != -ENOSPC)
55 		RET_CG_ERR(ret);
56 
57 	/* Test searching magic with regular kind */
58 	hdr.reg_opt.len = 4;
59 	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
60 			       load_flags);
61 	if (ret != -EINVAL)
62 		RET_CG_ERR(ret);
63 
64 	hdr.reg_opt.len = 0;
65 	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
66 			       load_flags);
67 	if (ret != 4 || hdr.reg_opt.len != 4 || hdr.reg_opt.kind != 0xB9 ||
68 	    hdr.reg_opt.data[0] != 0xfa || hdr.reg_opt.data[1] != 0xce)
69 		RET_CG_ERR(ret);
70 
71 	/* Test searching experimental option with invalid kind length */
72 	hdr.exprm_opt.kind = TCPOPT_EXP;
73 	hdr.exprm_opt.len = 5;
74 	hdr.exprm_opt.magic = 0;
75 	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
76 			       load_flags);
77 	if (ret != -EINVAL)
78 		RET_CG_ERR(ret);
79 
80 	/* Test searching experimental option with 0 magic value */
81 	hdr.exprm_opt.len = 4;
82 	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
83 			       load_flags);
84 	if (ret != -ENOMSG)
85 		RET_CG_ERR(ret);
86 
87 	hdr.exprm_opt.magic = __bpf_htons(0xeB9F);
88 	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
89 			       load_flags);
90 	if (ret != 4 || hdr.exprm_opt.len != 4 ||
91 	    hdr.exprm_opt.kind != TCPOPT_EXP ||
92 	    hdr.exprm_opt.magic != __bpf_htons(0xeB9F))
93 		RET_CG_ERR(ret);
94 
95 	if (!check_syn)
96 		return CG_OK;
97 
98 	/* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV
99 	 *
100 	 * Test loading from tp->saved_syn for other sk_state.
101 	 */
102 	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6,
103 			     sizeof(hdr.ip6));
104 	if (ret != -ENOSPC)
105 		RET_CG_ERR(ret);
106 
107 	if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n ||
108 	    hdr.ip6.daddr.s6_addr16[7] != last_addr16_n)
109 		RET_CG_ERR(0);
110 
111 	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr));
112 	if (ret < 0)
113 		RET_CG_ERR(ret);
114 
115 	pth = (struct tcphdr *)(&hdr.ip6 + 1);
116 	if (pth->dest != passive_lport_n || pth->source != active_lport_n)
117 		RET_CG_ERR(0);
118 
119 	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr));
120 	if (ret < 0)
121 		RET_CG_ERR(ret);
122 
123 	if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n)
124 		RET_CG_ERR(0);
125 
126 	return CG_OK;
127 }
128 
check_active_syn_in(struct bpf_sock_ops * skops)129 static int check_active_syn_in(struct bpf_sock_ops *skops)
130 {
131 	return __check_active_hdr_in(skops, true);
132 }
133 
check_active_hdr_in(struct bpf_sock_ops * skops)134 static int check_active_hdr_in(struct bpf_sock_ops *skops)
135 {
136 	struct tcphdr *th;
137 
138 	if (__check_active_hdr_in(skops, false) == CG_ERR)
139 		return CG_ERR;
140 
141 	th = skops->skb_data;
142 	if (th + 1 > skops->skb_data_end)
143 		RET_CG_ERR(0);
144 
145 	if (tcp_hdrlen(th) < skops->skb_len)
146 		nr_data++;
147 
148 	if (th->fin)
149 		nr_fin++;
150 
151 	if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len)
152 		nr_pure_ack++;
153 
154 	if (skops->skb_hwtstamp)
155 		nr_hwtstamp++;
156 
157 	return CG_OK;
158 }
159 
active_opt_len(struct bpf_sock_ops * skops)160 static int active_opt_len(struct bpf_sock_ops *skops)
161 {
162 	int err;
163 
164 	/* Reserve more than enough to allow the -EEXIST test in
165 	 * the write_active_opt().
166 	 */
167 	err = bpf_reserve_hdr_opt(skops, 12, 0);
168 	if (err)
169 		RET_CG_ERR(err);
170 
171 	return CG_OK;
172 }
173 
write_active_opt(struct bpf_sock_ops * skops)174 static int write_active_opt(struct bpf_sock_ops *skops)
175 {
176 	struct tcp_exprm_opt exprm_opt = {};
177 	struct tcp_opt win_scale_opt = {};
178 	struct tcp_opt reg_opt = {};
179 	struct tcphdr *th;
180 	int err, ret;
181 
182 	exprm_opt.kind = TCPOPT_EXP;
183 	exprm_opt.len = 4;
184 	exprm_opt.magic = __bpf_htons(0xeB9F);
185 
186 	reg_opt.kind = 0xB9;
187 	reg_opt.len = 4;
188 	reg_opt.data[0] = 0xfa;
189 	reg_opt.data[1] = 0xce;
190 
191 	win_scale_opt.kind = TCPOPT_WINDOW;
192 
193 	err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
194 	if (err)
195 		RET_CG_ERR(err);
196 
197 	/* Store the same exprm option */
198 	err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
199 	if (err != -EEXIST)
200 		RET_CG_ERR(err);
201 
202 	err = bpf_store_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
203 	if (err)
204 		RET_CG_ERR(err);
205 	err = bpf_store_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
206 	if (err != -EEXIST)
207 		RET_CG_ERR(err);
208 
209 	/* Check the option has been written and can be searched */
210 	ret = bpf_load_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
211 	if (ret != 4 || exprm_opt.len != 4 || exprm_opt.kind != TCPOPT_EXP ||
212 	    exprm_opt.magic != __bpf_htons(0xeB9F))
213 		RET_CG_ERR(ret);
214 
215 	reg_opt.len = 0;
216 	ret = bpf_load_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
217 	if (ret != 4 || reg_opt.len != 4 || reg_opt.kind != 0xB9 ||
218 	    reg_opt.data[0] != 0xfa || reg_opt.data[1] != 0xce)
219 		RET_CG_ERR(ret);
220 
221 	th = skops->skb_data;
222 	if (th + 1 > skops->skb_data_end)
223 		RET_CG_ERR(0);
224 
225 	if (th->syn) {
226 		active_lport_h = skops->local_port;
227 		active_lport_n = th->source;
228 
229 		/* Search the win scale option written by kernel
230 		 * in the SYN packet.
231 		 */
232 		ret = bpf_load_hdr_opt(skops, &win_scale_opt,
233 				       sizeof(win_scale_opt), 0);
234 		if (ret != 3 || win_scale_opt.len != 3 ||
235 		    win_scale_opt.kind != TCPOPT_WINDOW)
236 			RET_CG_ERR(ret);
237 
238 		/* Write the win scale option that kernel
239 		 * has already written.
240 		 */
241 		err = bpf_store_hdr_opt(skops, &win_scale_opt,
242 					sizeof(win_scale_opt), 0);
243 		if (err != -EEXIST)
244 			RET_CG_ERR(err);
245 	}
246 
247 	return CG_OK;
248 }
249 
handle_hdr_opt_len(struct bpf_sock_ops * skops)250 static int handle_hdr_opt_len(struct bpf_sock_ops *skops)
251 {
252 	__u8 tcp_flags = skops_tcp_flags(skops);
253 
254 	if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK)
255 		/* Check the SYN from bpf_sock_ops_kern->syn_skb */
256 		return check_active_syn_in(skops);
257 
258 	/* Passive side should have cleared the write hdr cb by now */
259 	if (skops->local_port == passive_lport_h)
260 		RET_CG_ERR(0);
261 
262 	return active_opt_len(skops);
263 }
264 
handle_write_hdr_opt(struct bpf_sock_ops * skops)265 static int handle_write_hdr_opt(struct bpf_sock_ops *skops)
266 {
267 	if (skops->local_port == passive_lport_h)
268 		RET_CG_ERR(0);
269 
270 	return write_active_opt(skops);
271 }
272 
handle_parse_hdr(struct bpf_sock_ops * skops)273 static int handle_parse_hdr(struct bpf_sock_ops *skops)
274 {
275 	/* Passive side is not writing any non-standard/unknown
276 	 * option, so the active side should never be called.
277 	 */
278 	if (skops->local_port == active_lport_h)
279 		RET_CG_ERR(0);
280 
281 	return check_active_hdr_in(skops);
282 }
283 
handle_passive_estab(struct bpf_sock_ops * skops)284 static int handle_passive_estab(struct bpf_sock_ops *skops)
285 {
286 	int err;
287 
288 	/* No more write hdr cb */
289 	bpf_sock_ops_cb_flags_set(skops,
290 				  skops->bpf_sock_ops_cb_flags &
291 				  ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);
292 
293 	/* Recheck the SYN but check the tp->saved_syn this time */
294 	err = check_active_syn_in(skops);
295 	if (err == CG_ERR)
296 		return err;
297 
298 	nr_syn++;
299 
300 	/* The ack has header option written by the active side also */
301 	return check_active_hdr_in(skops);
302 }
303 
304 SEC("sockops")
misc_estab(struct bpf_sock_ops * skops)305 int misc_estab(struct bpf_sock_ops *skops)
306 {
307 	int true_val = 1, false_val = 0, ret;
308 
309 	switch (skops->op) {
310 	case BPF_SOCK_OPS_TCP_LISTEN_CB:
311 		passive_lport_h = skops->local_port;
312 		passive_lport_n = __bpf_htons(passive_lport_h);
313 		bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN,
314 			       &true_val, sizeof(true_val));
315 		set_hdr_cb_flags(skops, 0);
316 		break;
317 	case BPF_SOCK_OPS_TCP_CONNECT_CB:
318 		set_hdr_cb_flags(skops, 0);
319 		break;
320 	case BPF_SOCK_OPS_PARSE_HDR_OPT_CB:
321 		return handle_parse_hdr(skops);
322 	case BPF_SOCK_OPS_HDR_OPT_LEN_CB:
323 		ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
324 		if (ret == -EOPNOTSUPP)
325 			nodelay_hdr_len_reject = true;
326 		return handle_hdr_opt_len(skops);
327 	case BPF_SOCK_OPS_WRITE_HDR_OPT_CB:
328 		ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &true_val, sizeof(true_val));
329 		if (ret == -EOPNOTSUPP)
330 			nodelay_write_hdr_reject = true;
331 		return handle_write_hdr_opt(skops);
332 	case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
333 		ret = bpf_setsockopt(skops, SOL_TCP, TCP_NODELAY, &false_val, sizeof(false_val));
334 		if (!ret)
335 			nodelay_est_ok = true;
336 		return handle_passive_estab(skops);
337 	}
338 
339 	return CG_OK;
340 }
341 
342 char _license[] SEC("license") = "GPL";
343