summaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/bpf/prog_tests/btf_skc_cls_ingress.c
blob: cf15cc3be49105db2a47d5c8686db7236ce9dd09 (plain)
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
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */

#define _GNU_SOURCE
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sched.h>
#include <net/if.h>
#include <linux/compiler.h>
#include <bpf/libbpf.h>

#include "network_helpers.h"
#include "test_progs.h"
#include "test_btf_skc_cls_ingress.skel.h"

#define TEST_NS "skc_cls_ingress"

#define BIT(n)		(1 << (n))
#define TEST_MODE_IPV4	BIT(0)
#define TEST_MODE_IPV6	BIT(1)
#define TEST_MODE_DUAL	(TEST_MODE_IPV4 | TEST_MODE_IPV6)

#define SERVER_ADDR_IPV4	"127.0.0.1"
#define SERVER_ADDR_IPV6	"::1"
#define SERVER_ADDR_DUAL	"::0"
/* RFC791, 576 for minimal IPv4 datagram, minus 40 bytes of TCP header */
#define MIN_IPV4_MSS		536

static struct netns_obj *prepare_netns(struct test_btf_skc_cls_ingress *skel)
{
	LIBBPF_OPTS(bpf_tc_hook, qdisc_lo, .attach_point = BPF_TC_INGRESS);
	LIBBPF_OPTS(bpf_tc_opts, tc_attach,
		    .prog_fd = bpf_program__fd(skel->progs.cls_ingress));
	struct netns_obj *ns = NULL;

	ns = netns_new(TEST_NS, true);
	if (!ASSERT_OK_PTR(ns, "create and join netns"))
		return ns;

	qdisc_lo.ifindex = if_nametoindex("lo");
	if (!ASSERT_OK(bpf_tc_hook_create(&qdisc_lo), "qdisc add dev lo clsact"))
		goto free_ns;

	if (!ASSERT_OK(bpf_tc_attach(&qdisc_lo, &tc_attach),
		       "filter add dev lo ingress"))
		goto free_ns;

	/* Ensure 20 bytes options (i.e. in total 40 bytes tcp header) for the
	 * bpf_tcp_gen_syncookie() helper.
	 */
	if (write_sysctl("/proc/sys/net/ipv4/tcp_window_scaling", "1") ||
	    write_sysctl("/proc/sys/net/ipv4/tcp_timestamps", "1") ||
	    write_sysctl("/proc/sys/net/ipv4/tcp_sack", "1"))
		goto free_ns;

	return ns;

free_ns:
	netns_free(ns);
	return NULL;
}

static void reset_test(struct test_btf_skc_cls_ingress *skel)
{
	memset(&skel->bss->srv_sa4, 0, sizeof(skel->bss->srv_sa4));
	memset(&skel->bss->srv_sa6, 0, sizeof(skel->bss->srv_sa6));
	skel->bss->listen_tp_sport = 0;
	skel->bss->req_sk_sport = 0;
	skel->bss->recv_cookie = 0;
	skel->bss->gen_cookie = 0;
	skel->bss->linum = 0;
	skel->bss->mss = 0;
}

static void print_err_line(struct test_btf_skc_cls_ingress *skel)
{
	if (skel->bss->linum)
		printf("bpf prog error at line %u\n", skel->bss->linum);
}

static int v6only_true(int fd, void *opts)
{
	int mode = true;

	return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &mode, sizeof(mode));
}

static int v6only_false(int fd, void *opts)
{
	int mode = false;

	return setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &mode, sizeof(mode));
}

static void run_test(struct test_btf_skc_cls_ingress *skel, bool gen_cookies,
		     int ip_mode)
{
	const char *tcp_syncookies = gen_cookies ? "2" : "1";
	int listen_fd = -1, cli_fd = -1, srv_fd = -1, err;
	struct network_helper_opts opts = { 0 };
	struct sockaddr_storage *addr;
	struct sockaddr_in6 srv_sa6;
	struct sockaddr_in srv_sa4;
	socklen_t addr_len;
	int sock_family;
	char *srv_addr;
	int srv_port;

	switch (ip_mode) {
	case TEST_MODE_IPV4:
		sock_family = AF_INET;
		srv_addr = SERVER_ADDR_IPV4;
		addr = (struct sockaddr_storage *)&srv_sa4;
		addr_len = sizeof(srv_sa4);
		break;
	case TEST_MODE_IPV6:
		opts.post_socket_cb = v6only_true;
		sock_family = AF_INET6;
		srv_addr = SERVER_ADDR_IPV6;
		addr = (struct sockaddr_storage *)&srv_sa6;
		addr_len = sizeof(srv_sa6);
		break;
	case TEST_MODE_DUAL:
		opts.post_socket_cb = v6only_false;
		sock_family = AF_INET6;
		srv_addr = SERVER_ADDR_DUAL;
		addr = (struct sockaddr_storage *)&srv_sa6;
		addr_len = sizeof(srv_sa6);
		break;
	default:
		PRINT_FAIL("Unknown IP mode %d", ip_mode);
		return;
	}

	if (write_sysctl("/proc/sys/net/ipv4/tcp_syncookies", tcp_syncookies))
		return;

	listen_fd = start_server_str(sock_family, SOCK_STREAM, srv_addr,  0,
				     &opts);
	if (!ASSERT_OK_FD(listen_fd, "start server"))
		return;

	err = getsockname(listen_fd, (struct sockaddr *)addr, &addr_len);
	if (!ASSERT_OK(err, "getsockname(listen_fd)"))
		goto done;

	switch (ip_mode) {
	case TEST_MODE_IPV4:
		memcpy(&skel->bss->srv_sa4, &srv_sa4, sizeof(srv_sa4));
		srv_port = ntohs(srv_sa4.sin_port);
		break;
	case TEST_MODE_IPV6:
	case TEST_MODE_DUAL:
		memcpy(&skel->bss->srv_sa6, &srv_sa6, sizeof(srv_sa6));
		srv_port = ntohs(srv_sa6.sin6_port);
		break;
	default:
		goto done;
	}

	cli_fd = connect_to_fd(listen_fd, 0);
	if (!ASSERT_OK_FD(cli_fd, "connect client"))
		goto done;

	srv_fd = accept(listen_fd, NULL, NULL);
	if (!ASSERT_OK_FD(srv_fd, "accept connection"))
		goto done;

	ASSERT_EQ(skel->bss->listen_tp_sport, srv_port, "listen tp src port");

	if (!gen_cookies) {
		ASSERT_EQ(skel->bss->req_sk_sport, srv_port,
			  "request socket source port with syncookies disabled");
		ASSERT_EQ(skel->bss->gen_cookie, 0,
			  "generated syncookie with syncookies disabled");
		ASSERT_EQ(skel->bss->recv_cookie, 0,
			  "received syncookie with syncookies disabled");
	} else {
		ASSERT_EQ(skel->bss->req_sk_sport, 0,
			  "request socket source port with syncookies enabled");
		ASSERT_NEQ(skel->bss->gen_cookie, 0,
			   "syncookie properly generated");
		ASSERT_EQ(skel->bss->gen_cookie, skel->bss->recv_cookie,
			  "matching syncookies on client and server");
		ASSERT_GT(skel->bss->mss, MIN_IPV4_MSS,
			  "MSS in cookie min value");
		ASSERT_LT(skel->bss->mss, USHRT_MAX,
			  "MSS in cookie max value");
	}

done:
	if (listen_fd != -1)
		close(listen_fd);
	if (cli_fd != -1)
		close(cli_fd);
	if (srv_fd != -1)
		close(srv_fd);
}

static void test_conn_ipv4(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, false, TEST_MODE_IPV4);
}

static void test_conn_ipv6(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, false, TEST_MODE_IPV6);
}

static void test_conn_dual(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, false, TEST_MODE_DUAL);
}

static void test_syncookie_ipv4(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, true, TEST_MODE_IPV4);
}

static void test_syncookie_ipv6(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, true, TEST_MODE_IPV6);
}

static void test_syncookie_dual(struct test_btf_skc_cls_ingress *skel)
{
	run_test(skel, true, TEST_MODE_DUAL);
}

struct test {
	const char *desc;
	void (*run)(struct test_btf_skc_cls_ingress *skel);
};

#define DEF_TEST(name) { #name, test_##name }
static struct test tests[] = {
	DEF_TEST(conn_ipv4),
	DEF_TEST(conn_ipv6),
	DEF_TEST(conn_dual),
	DEF_TEST(syncookie_ipv4),
	DEF_TEST(syncookie_ipv6),
	DEF_TEST(syncookie_dual),
};

void test_btf_skc_cls_ingress(void)
{
	struct test_btf_skc_cls_ingress *skel;
	struct netns_obj *ns;
	int i;

	skel = test_btf_skc_cls_ingress__open_and_load();
	if (!ASSERT_OK_PTR(skel, "test_btf_skc_cls_ingress__open_and_load"))
		return;

	for (i = 0; i < ARRAY_SIZE(tests); i++) {
		if (!test__start_subtest(tests[i].desc))
			continue;

		ns = prepare_netns(skel);
		if (!ns)
			break;

		tests[i].run(skel);

		print_err_line(skel);
		reset_test(skel);
		netns_free(ns);
	}

	test_btf_skc_cls_ingress__destroy(skel);
}