aboutsummaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/bpf/progs/test_misc_tcp_hdr_options.c
blob: 2c121c5d66a7faeedaad032ab66cce8fcde89bee (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */

#include <stddef.h>
#include <errno.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/socket.h>
#include <linux/bpf.h>
#include <linux/types.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#define BPF_PROG_TEST_TCP_HDR_OPTIONS
#include "test_tcp_hdr_options.h"

__u16 last_addr16_n = __bpf_htons(1);
__u16 active_lport_n = 0;
__u16 active_lport_h = 0;
__u16 passive_lport_n = 0;
__u16 passive_lport_h = 0;

/* options received at passive side */
unsigned int nr_pure_ack = 0;
unsigned int nr_data = 0;
unsigned int nr_syn = 0;
unsigned int nr_fin = 0;

/* Check the header received from the active side */
static int __check_active_hdr_in(struct bpf_sock_ops *skops, bool check_syn)
{
	union {
		struct tcphdr th;
		struct ipv6hdr ip6;
		struct tcp_exprm_opt exprm_opt;
		struct tcp_opt reg_opt;
		__u8 data[100]; /* IPv6 (40) + Max TCP hdr (60) */
	} hdr = {};
	__u64 load_flags = check_syn ? BPF_LOAD_HDR_OPT_TCP_SYN : 0;
	struct tcphdr *pth;
	int ret;

	hdr.reg_opt.kind = 0xB9;

	/* The option is 4 bytes long instead of 2 bytes */
	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, 2, load_flags);
	if (ret != -ENOSPC)
		RET_CG_ERR(ret);

	/* Test searching magic with regular kind */
	hdr.reg_opt.len = 4;
	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
			       load_flags);
	if (ret != -EINVAL)
		RET_CG_ERR(ret);

	hdr.reg_opt.len = 0;
	ret = bpf_load_hdr_opt(skops, &hdr.reg_opt, sizeof(hdr.reg_opt),
			       load_flags);
	if (ret != 4 || hdr.reg_opt.len != 4 || hdr.reg_opt.kind != 0xB9 ||
	    hdr.reg_opt.data[0] != 0xfa || hdr.reg_opt.data[1] != 0xce)
		RET_CG_ERR(ret);

	/* Test searching experimental option with invalid kind length */
	hdr.exprm_opt.kind = TCPOPT_EXP;
	hdr.exprm_opt.len = 5;
	hdr.exprm_opt.magic = 0;
	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
			       load_flags);
	if (ret != -EINVAL)
		RET_CG_ERR(ret);

	/* Test searching experimental option with 0 magic value */
	hdr.exprm_opt.len = 4;
	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
			       load_flags);
	if (ret != -ENOMSG)
		RET_CG_ERR(ret);

	hdr.exprm_opt.magic = __bpf_htons(0xeB9F);
	ret = bpf_load_hdr_opt(skops, &hdr.exprm_opt, sizeof(hdr.exprm_opt),
			       load_flags);
	if (ret != 4 || hdr.exprm_opt.len != 4 ||
	    hdr.exprm_opt.kind != TCPOPT_EXP ||
	    hdr.exprm_opt.magic != __bpf_htons(0xeB9F))
		RET_CG_ERR(ret);

	if (!check_syn)
		return CG_OK;

	/* Test loading from skops->syn_skb if sk_state == TCP_NEW_SYN_RECV
	 *
	 * Test loading from tp->saved_syn for other sk_state.
	 */
	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr.ip6,
			     sizeof(hdr.ip6));
	if (ret != -ENOSPC)
		RET_CG_ERR(ret);

	if (hdr.ip6.saddr.s6_addr16[7] != last_addr16_n ||
	    hdr.ip6.daddr.s6_addr16[7] != last_addr16_n)
		RET_CG_ERR(0);

	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN_IP, &hdr, sizeof(hdr));
	if (ret < 0)
		RET_CG_ERR(ret);

	pth = (struct tcphdr *)(&hdr.ip6 + 1);
	if (pth->dest != passive_lport_n || pth->source != active_lport_n)
		RET_CG_ERR(0);

	ret = bpf_getsockopt(skops, SOL_TCP, TCP_BPF_SYN, &hdr, sizeof(hdr));
	if (ret < 0)
		RET_CG_ERR(ret);

	if (hdr.th.dest != passive_lport_n || hdr.th.source != active_lport_n)
		RET_CG_ERR(0);

	return CG_OK;
}

static int check_active_syn_in(struct bpf_sock_ops *skops)
{
	return __check_active_hdr_in(skops, true);
}

static int check_active_hdr_in(struct bpf_sock_ops *skops)
{
	struct tcphdr *th;

	if (__check_active_hdr_in(skops, false) == CG_ERR)
		return CG_ERR;

	th = skops->skb_data;
	if (th + 1 > skops->skb_data_end)
		RET_CG_ERR(0);

	if (tcp_hdrlen(th) < skops->skb_len)
		nr_data++;

	if (th->fin)
		nr_fin++;

	if (th->ack && !th->fin && tcp_hdrlen(th) == skops->skb_len)
		nr_pure_ack++;

	return CG_OK;
}

static int active_opt_len(struct bpf_sock_ops *skops)
{
	int err;

	/* Reserve more than enough to allow the -EEXIST test in
	 * the write_active_opt().
	 */
	err = bpf_reserve_hdr_opt(skops, 12, 0);
	if (err)
		RET_CG_ERR(err);

	return CG_OK;
}

static int write_active_opt(struct bpf_sock_ops *skops)
{
	struct tcp_exprm_opt exprm_opt = {};
	struct tcp_opt win_scale_opt = {};
	struct tcp_opt reg_opt = {};
	struct tcphdr *th;
	int err, ret;

	exprm_opt.kind = TCPOPT_EXP;
	exprm_opt.len = 4;
	exprm_opt.magic = __bpf_htons(0xeB9F);

	reg_opt.kind = 0xB9;
	reg_opt.len = 4;
	reg_opt.data[0] = 0xfa;
	reg_opt.data[1] = 0xce;

	win_scale_opt.kind = TCPOPT_WINDOW;

	err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
	if (err)
		RET_CG_ERR(err);

	/* Store the same exprm option */
	err = bpf_store_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
	if (err != -EEXIST)
		RET_CG_ERR(err);

	err = bpf_store_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
	if (err)
		RET_CG_ERR(err);
	err = bpf_store_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
	if (err != -EEXIST)
		RET_CG_ERR(err);

	/* Check the option has been written and can be searched */
	ret = bpf_load_hdr_opt(skops, &exprm_opt, sizeof(exprm_opt), 0);
	if (ret != 4 || exprm_opt.len != 4 || exprm_opt.kind != TCPOPT_EXP ||
	    exprm_opt.magic != __bpf_htons(0xeB9F))
		RET_CG_ERR(ret);

	reg_opt.len = 0;
	ret = bpf_load_hdr_opt(skops, &reg_opt, sizeof(reg_opt), 0);
	if (ret != 4 || reg_opt.len != 4 || reg_opt.kind != 0xB9 ||
	    reg_opt.data[0] != 0xfa || reg_opt.data[1] != 0xce)
		RET_CG_ERR(ret);

	th = skops->skb_data;
	if (th + 1 > skops->skb_data_end)
		RET_CG_ERR(0);

	if (th->syn) {
		active_lport_h = skops->local_port;
		active_lport_n = th->source;

		/* Search the win scale option written by kernel
		 * in the SYN packet.
		 */
		ret = bpf_load_hdr_opt(skops, &win_scale_opt,
				       sizeof(win_scale_opt), 0);
		if (ret != 3 || win_scale_opt.len != 3 ||
		    win_scale_opt.kind != TCPOPT_WINDOW)
			RET_CG_ERR(ret);

		/* Write the win scale option that kernel
		 * has already written.
		 */
		err = bpf_store_hdr_opt(skops, &win_scale_opt,
					sizeof(win_scale_opt), 0);
		if (err != -EEXIST)
			RET_CG_ERR(err);
	}

	return CG_OK;
}

static int handle_hdr_opt_len(struct bpf_sock_ops *skops)
{
	__u8 tcp_flags = skops_tcp_flags(skops);

	if ((tcp_flags & TCPHDR_SYNACK) == TCPHDR_SYNACK)
		/* Check the SYN from bpf_sock_ops_kern->syn_skb */
		return check_active_syn_in(skops);

	/* Passive side should have cleared the write hdr cb by now */
	if (skops->local_port == passive_lport_h)
		RET_CG_ERR(0);

	return active_opt_len(skops);
}

static int handle_write_hdr_opt(struct bpf_sock_ops *skops)
{
	if (skops->local_port == passive_lport_h)
		RET_CG_ERR(0);

	return write_active_opt(skops);
}

static int handle_parse_hdr(struct bpf_sock_ops *skops)
{
	/* Passive side is not writing any non-standard/unknown
	 * option, so the active side should never be called.
	 */
	if (skops->local_port == active_lport_h)
		RET_CG_ERR(0);

	return check_active_hdr_in(skops);
}

static int handle_passive_estab(struct bpf_sock_ops *skops)
{
	int err;

	/* No more write hdr cb */
	bpf_sock_ops_cb_flags_set(skops,
				  skops->bpf_sock_ops_cb_flags &
				  ~BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG);

	/* Recheck the SYN but check the tp->saved_syn this time */
	err = check_active_syn_in(skops);
	if (err == CG_ERR)
		return err;

	nr_syn++;

	/* The ack has header option written by the active side also */
	return check_active_hdr_in(skops);
}

SEC("sockops")
int misc_estab(struct bpf_sock_ops *skops)
{
	int true_val = 1;

	switch (skops->op) {
	case BPF_SOCK_OPS_TCP_LISTEN_CB:
		passive_lport_h = skops->local_port;
		passive_lport_n = __bpf_htons(passive_lport_h);
		bpf_setsockopt(skops, SOL_TCP, TCP_SAVE_SYN,
			       &true_val, sizeof(true_val));
		set_hdr_cb_flags(skops, 0);
		break;
	case BPF_SOCK_OPS_TCP_CONNECT_CB:
		set_hdr_cb_flags(skops, 0);
		break;
	case BPF_SOCK_OPS_PARSE_HDR_OPT_CB:
		return handle_parse_hdr(skops);
	case BPF_SOCK_OPS_HDR_OPT_LEN_CB:
		return handle_hdr_opt_len(skops);
	case BPF_SOCK_OPS_WRITE_HDR_OPT_CB:
		return handle_write_hdr_opt(skops);
	case BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB:
		return handle_passive_estab(skops);
	}

	return CG_OK;
}

char _license[] SEC("license") = "GPL";