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