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;
31 /* Check the header received from the active side */
32 static int __check_active_hdr_in(struct bpf_sock_ops
*skops
, bool check_syn
)
37 struct tcp_exprm_opt exprm_opt
;
38 struct tcp_opt reg_opt
;
39 __u8 data
[100]; /* IPv6 (40) + Max TCP hdr (60) */
41 __u64 load_flags
= check_syn
? BPF_LOAD_HDR_OPT_TCP_SYN
: 0;
45 hdr
.reg_opt
.kind
= 0xB9;
47 /* The option is 4 bytes long instead of 2 bytes */
48 ret
= bpf_load_hdr_opt(skops
, &hdr
.reg_opt
, 2, load_flags
);
52 /* Test searching magic with regular kind */
54 ret
= bpf_load_hdr_opt(skops
, &hdr
.reg_opt
, sizeof(hdr
.reg_opt
),
60 ret
= bpf_load_hdr_opt(skops
, &hdr
.reg_opt
, sizeof(hdr
.reg_opt
),
62 if (ret
!= 4 || hdr
.reg_opt
.len
!= 4 || hdr
.reg_opt
.kind
!= 0xB9 ||
63 hdr
.reg_opt
.data
[0] != 0xfa || hdr
.reg_opt
.data
[1] != 0xce)
66 /* Test searching experimental option with invalid kind length */
67 hdr
.exprm_opt
.kind
= TCPOPT_EXP
;
68 hdr
.exprm_opt
.len
= 5;
69 hdr
.exprm_opt
.magic
= 0;
70 ret
= bpf_load_hdr_opt(skops
, &hdr
.exprm_opt
, sizeof(hdr
.exprm_opt
),
75 /* Test searching experimental option with 0 magic value */
76 hdr
.exprm_opt
.len
= 4;
77 ret
= bpf_load_hdr_opt(skops
, &hdr
.exprm_opt
, sizeof(hdr
.exprm_opt
),
82 hdr
.exprm_opt
.magic
= __bpf_htons(0xeB9F);
83 ret
= bpf_load_hdr_opt(skops
, &hdr
.exprm_opt
, sizeof(hdr
.exprm_opt
),
85 if (ret
!= 4 || hdr
.exprm_opt
.len
!= 4 ||
86 hdr
.exprm_opt
.kind
!= TCPOPT_EXP
||
87 hdr
.exprm_opt
.magic
!= __bpf_htons(0xeB9F))
93 /* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV
95 * Test loading from tp->saved_syn for other sk_state.
97 ret
= bpf_getsockopt(skops
, SOL_TCP
, TCP_BPF_SYN_IP
, &hdr
.ip6
,
102 if (hdr
.ip6
.saddr
.s6_addr16
[7] != last_addr16_n
||
103 hdr
.ip6
.daddr
.s6_addr16
[7] != last_addr16_n
)
106 ret
= bpf_getsockopt(skops
, SOL_TCP
, TCP_BPF_SYN_IP
, &hdr
, sizeof(hdr
));
110 pth
= (struct tcphdr
*)(&hdr
.ip6
+ 1);
111 if (pth
->dest
!= passive_lport_n
|| pth
->source
!= active_lport_n
)
114 ret
= bpf_getsockopt(skops
, SOL_TCP
, TCP_BPF_SYN
, &hdr
, sizeof(hdr
));
118 if (hdr
.th
.dest
!= passive_lport_n
|| hdr
.th
.source
!= active_lport_n
)
124 static int check_active_syn_in(struct bpf_sock_ops
*skops
)
126 return __check_active_hdr_in(skops
, true);
129 static int check_active_hdr_in(struct bpf_sock_ops
*skops
)
133 if (__check_active_hdr_in(skops
, false) == CG_ERR
)
136 th
= skops
->skb_data
;
137 if (th
+ 1 > skops
->skb_data_end
)
140 if (tcp_hdrlen(th
) < skops
->skb_len
)
146 if (th
->ack
&& !th
->fin
&& tcp_hdrlen(th
) == skops
->skb_len
)
152 static int active_opt_len(struct bpf_sock_ops
*skops
)
156 /* Reserve more than enough to allow the -EEXIST test in
157 * the write_active_opt().
159 err
= bpf_reserve_hdr_opt(skops
, 12, 0);
166 static int write_active_opt(struct bpf_sock_ops
*skops
)
168 struct tcp_exprm_opt exprm_opt
= {};
169 struct tcp_opt win_scale_opt
= {};
170 struct tcp_opt reg_opt
= {};
174 exprm_opt
.kind
= TCPOPT_EXP
;
176 exprm_opt
.magic
= __bpf_htons(0xeB9F);
180 reg_opt
.data
[0] = 0xfa;
181 reg_opt
.data
[1] = 0xce;
183 win_scale_opt
.kind
= TCPOPT_WINDOW
;
185 err
= bpf_store_hdr_opt(skops
, &exprm_opt
, sizeof(exprm_opt
), 0);
189 /* Store the same exprm option */
190 err
= bpf_store_hdr_opt(skops
, &exprm_opt
, sizeof(exprm_opt
), 0);
194 err
= bpf_store_hdr_opt(skops
, ®_opt
, sizeof(reg_opt
), 0);
197 err
= bpf_store_hdr_opt(skops
, ®_opt
, sizeof(reg_opt
), 0);
201 /* Check the option has been written and can be searched */
202 ret
= bpf_load_hdr_opt(skops
, &exprm_opt
, sizeof(exprm_opt
), 0);
203 if (ret
!= 4 || exprm_opt
.len
!= 4 || exprm_opt
.kind
!= TCPOPT_EXP
||
204 exprm_opt
.magic
!= __bpf_htons(0xeB9F))
208 ret
= bpf_load_hdr_opt(skops
, ®_opt
, sizeof(reg_opt
), 0);
209 if (ret
!= 4 || reg_opt
.len
!= 4 || reg_opt
.kind
!= 0xB9 ||
210 reg_opt
.data
[0] != 0xfa || reg_opt
.data
[1] != 0xce)
213 th
= skops
->skb_data
;
214 if (th
+ 1 > skops
->skb_data_end
)
218 active_lport_h
= skops
->local_port
;
219 active_lport_n
= th
->source
;
221 /* Search the win scale option written by kernel
224 ret
= bpf_load_hdr_opt(skops
, &win_scale_opt
,
225 sizeof(win_scale_opt
), 0);
226 if (ret
!= 3 || win_scale_opt
.len
!= 3 ||
227 win_scale_opt
.kind
!= TCPOPT_WINDOW
)
230 /* Write the win scale option that kernel
231 * has already written.
233 err
= bpf_store_hdr_opt(skops
, &win_scale_opt
,
234 sizeof(win_scale_opt
), 0);
242 static int handle_hdr_opt_len(struct bpf_sock_ops
*skops
)
244 __u8 tcp_flags
= skops_tcp_flags(skops
);
246 if ((tcp_flags
& TCPHDR_SYNACK
) == TCPHDR_SYNACK
)
247 /* Check the SYN from bpf_sock_ops_kern->syn_skb */
248 return check_active_syn_in(skops
);
250 /* Passive side should have cleared the write hdr cb by now */
251 if (skops
->local_port
== passive_lport_h
)
254 return active_opt_len(skops
);
257 static int handle_write_hdr_opt(struct bpf_sock_ops
*skops
)
259 if (skops
->local_port
== passive_lport_h
)
262 return write_active_opt(skops
);
265 static int handle_parse_hdr(struct bpf_sock_ops
*skops
)
267 /* Passive side is not writing any non-standard/unknown
268 * option, so the active side should never be called.
270 if (skops
->local_port
== active_lport_h
)
273 return check_active_hdr_in(skops
);
276 static int handle_passive_estab(struct bpf_sock_ops
*skops
)
280 /* No more write hdr cb */
281 bpf_sock_ops_cb_flags_set(skops
,
282 skops
->bpf_sock_ops_cb_flags
&
283 ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG
);
285 /* Recheck the SYN but check the tp->saved_syn this time */
286 err
= check_active_syn_in(skops
);
292 /* The ack has header option written by the active side also */
293 return check_active_hdr_in(skops
);
296 SEC("sockops/misc_estab")
297 int misc_estab(struct bpf_sock_ops
*skops
)
302 case BPF_SOCK_OPS_TCP_LISTEN_CB
:
303 passive_lport_h
= skops
->local_port
;
304 passive_lport_n
= __bpf_htons(passive_lport_h
);
305 bpf_setsockopt(skops
, SOL_TCP
, TCP_SAVE_SYN
,
306 &true_val
, sizeof(true_val
));
307 set_hdr_cb_flags(skops
, 0);
309 case BPF_SOCK_OPS_TCP_CONNECT_CB
:
310 set_hdr_cb_flags(skops
, 0);
312 case BPF_SOCK_OPS_PARSE_HDR_OPT_CB
:
313 return handle_parse_hdr(skops
);
314 case BPF_SOCK_OPS_HDR_OPT_LEN_CB
:
315 return handle_hdr_opt_len(skops
);
316 case BPF_SOCK_OPS_WRITE_HDR_OPT_CB
:
317 return handle_write_hdr_opt(skops
);
318 case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB
:
319 return handle_passive_estab(skops
);
325 char _license
[] SEC("license") = "GPL";