1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2020 Facebook */
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"
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;
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;
32 /* Check the header received from the active side */
33 static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn)
38 struct tcp_exprm_opt exprm_opt;
39 struct tcp_opt reg_opt;
40 __u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */
42 __u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0;
46 hdr.reg_opt.kind = 0xB9;
48 /* The option is 4 bytes long instead of 2 bytes */
49 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags);
53 /* Test searching magic with regular kind */
55 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
61 ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
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)
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),
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),
83 hdr.exprm_opt.magic = __bpf_htons(0xeB9F);
84 ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
86 if (ret != 4 || hdr.exprm_opt.len != 4 ||
87 hdr.exprm_opt.kind != TCPOPT_EXP ||
88 hdr.exprm_opt.magic != __bpf_htons(0xeB9F))
94 /* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV
96 * Test loading from tp->saved_syn for other sk_state.
98 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6,
103 if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n ||
104 hdr.ip6.daddr.s6_addr16[7] != last_addr16_n)
107 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr));
111 pth = (struct tcphdr *)(&hdr.ip6 + 1);
112 if (pth->dest != passive_lport_n || pth->source != active_lport_n)
115 ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr));
119 if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n)
125 static int check_active_syn_in(struct bpf_sock_ops *skops)
127 return __check_active_hdr_in(skops, true);
130 static int check_active_hdr_in(struct bpf_sock_ops *skops)
134 if (__check_active_hdr_in(skops, false) == CG_ERR)
137 th = skops->skb_data;
138 if (th + 1 > skops->skb_data_end)
141 if (tcp_hdrlen(th) < skops->skb_len)
147 if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len)
150 if (skops->skb_hwtstamp)
156 static int active_opt_len(struct bpf_sock_ops *skops)
160 /* Reserve more than enough to allow the -EEXIST test in
161 * the write_active_opt().
163 err = bpf_reserve_hdr_opt(skops, 12, 0);
170 static int write_active_opt(struct bpf_sock_ops *skops)
172 struct tcp_exprm_opt exprm_opt = {};
173 struct tcp_opt win_scale_opt = {};
174 struct tcp_opt reg_opt = {};
178 exprm_opt.kind = TCPOPT_EXP;
180 exprm_opt.magic = __bpf_htons(0xeB9F);
184 reg_opt.data[0] = 0xfa;
185 reg_opt.data[1] = 0xce;
187 win_scale_opt.kind = TCPOPT_WINDOW;
189 err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
193 /* Store the same exprm option */
194 err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
198 err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0);
201 err = bpf_store_hdr_opt(skops, ®_opt, sizeof(reg_opt), 0);
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))
212 ret = bpf_load_hdr_opt(skops, ®_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)
217 th = skops->skb_data;
218 if (th + 1 > skops->skb_data_end)
222 active_lport_h = skops->local_port;
223 active_lport_n = th->source;
225 /* Search the win scale option written by kernel
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)
234 /* Write the win scale option that kernel
235 * has already written.
237 err = bpf_store_hdr_opt(skops, &win_scale_opt,
238 sizeof(win_scale_opt), 0);
246 static int handle_hdr_opt_len(struct bpf_sock_ops *skops)
248 __u8 tcp_flags = skops_tcp_flags(skops);
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);
254 /* Passive side should have cleared the write hdr cb by now */
255 if (skops->local_port == passive_lport_h)
258 return active_opt_len(skops);
261 static int handle_write_hdr_opt(struct bpf_sock_ops *skops)
263 if (skops->local_port == passive_lport_h)
266 return write_active_opt(skops);
269 static int handle_parse_hdr(struct bpf_sock_ops *skops)
271 /* Passive side is not writing any non-standard/unknown
272 * option, so the active side should never be called.
274 if (skops->local_port == active_lport_h)
277 return check_active_hdr_in(skops);
280 static int handle_passive_estab(struct bpf_sock_ops *skops)
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);
289 /* Recheck the SYN but check the tp->saved_syn this time */
290 err = check_active_syn_in(skops);
296 /* The ack has header option written by the active side also */
297 return check_active_hdr_in(skops);
301 int misc_estab(struct bpf_sock_ops *skops)
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);
313 case BPF_SOCK_OPS_TCP_CONNECT_CB:
314 set_hdr_cb_flags(skops, 0);
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);
329 char _license[] SEC("license") = "GPL";