summaryrefslogtreecommitdiff
path: root/tools/testing/selftests/bpf/prog_tests/xdp_bonding.c
blob: ad3ba81b40480c06db4bd94b4d7cc833ca2b7ff8 (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
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
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
// SPDX-License-Identifier: GPL-2.0

/**
 * Test XDP bonding support
 *
 * Sets up two bonded veth pairs between two fresh namespaces
 * and verifies that XDP_TX program loaded on a bond device
 * are correctly loaded onto the slave devices and XDP_TX'd
 * packets are balanced using bonding.
 */

#define _GNU_SOURCE
#include <sched.h>
#include <net/if.h>
#include <linux/if_link.h>
#include "test_progs.h"
#include "network_helpers.h"
#include <linux/if_bonding.h>
#include <linux/limits.h>
#include <linux/udp.h>

#include "xdp_dummy.skel.h"
#include "xdp_redirect_multi_kern.skel.h"
#include "xdp_tx.skel.h"

#define BOND1_MAC {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}
#define BOND1_MAC_STR "00:11:22:33:44:55"
#define BOND2_MAC {0x00, 0x22, 0x33, 0x44, 0x55, 0x66}
#define BOND2_MAC_STR "00:22:33:44:55:66"
#define NPACKETS 100

static int root_netns_fd = -1;

static void restore_root_netns(void)
{
	ASSERT_OK(setns(root_netns_fd, CLONE_NEWNET), "restore_root_netns");
}

static int setns_by_name(char *name)
{
	int nsfd, err;
	char nspath[PATH_MAX];

	snprintf(nspath, sizeof(nspath), "%s/%s", "/var/run/netns", name);
	nsfd = open(nspath, O_RDONLY | O_CLOEXEC);
	if (nsfd < 0)
		return -1;

	err = setns(nsfd, CLONE_NEWNET);
	close(nsfd);
	return err;
}

static int get_rx_packets(const char *iface)
{
	FILE *f;
	char line[512];
	int iface_len = strlen(iface);

	f = fopen("/proc/net/dev", "r");
	if (!f)
		return -1;

	while (fgets(line, sizeof(line), f)) {
		char *p = line;

		while (*p == ' ')
			p++; /* skip whitespace */
		if (!strncmp(p, iface, iface_len)) {
			p += iface_len;
			if (*p++ != ':')
				continue;
			while (*p == ' ')
				p++; /* skip whitespace */
			while (*p && *p != ' ')
				p++; /* skip rx bytes */
			while (*p == ' ')
				p++; /* skip whitespace */
			fclose(f);
			return atoi(p);
		}
	}
	fclose(f);
	return -1;
}

#define MAX_BPF_LINKS 8

struct skeletons {
	struct xdp_dummy *xdp_dummy;
	struct xdp_tx *xdp_tx;
	struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;

	int nlinks;
	struct bpf_link *links[MAX_BPF_LINKS];
};

static int xdp_attach(struct skeletons *skeletons, struct bpf_program *prog, char *iface)
{
	struct bpf_link *link;
	int ifindex;

	ifindex = if_nametoindex(iface);
	if (!ASSERT_GT(ifindex, 0, "get ifindex"))
		return -1;

	if (!ASSERT_LE(skeletons->nlinks+1, MAX_BPF_LINKS, "too many XDP programs attached"))
		return -1;

	link = bpf_program__attach_xdp(prog, ifindex);
	if (!ASSERT_OK_PTR(link, "attach xdp program"))
		return -1;

	skeletons->links[skeletons->nlinks++] = link;
	return 0;
}

enum {
	BOND_ONE_NO_ATTACH = 0,
	BOND_BOTH_AND_ATTACH,
};

static const char * const mode_names[] = {
	[BOND_MODE_ROUNDROBIN]   = "balance-rr",
	[BOND_MODE_ACTIVEBACKUP] = "active-backup",
	[BOND_MODE_XOR]          = "balance-xor",
	[BOND_MODE_BROADCAST]    = "broadcast",
	[BOND_MODE_8023AD]       = "802.3ad",
	[BOND_MODE_TLB]          = "balance-tlb",
	[BOND_MODE_ALB]          = "balance-alb",
};

static const char * const xmit_policy_names[] = {
	[BOND_XMIT_POLICY_LAYER2]       = "layer2",
	[BOND_XMIT_POLICY_LAYER34]      = "layer3+4",
	[BOND_XMIT_POLICY_LAYER23]      = "layer2+3",
	[BOND_XMIT_POLICY_ENCAP23]      = "encap2+3",
	[BOND_XMIT_POLICY_ENCAP34]      = "encap3+4",
};

static int bonding_setup(struct skeletons *skeletons, int mode, int xmit_policy,
			 int bond_both_attach)
{
#define SYS(fmt, ...)						\
	({							\
		char cmd[1024];					\
		snprintf(cmd, sizeof(cmd), fmt, ##__VA_ARGS__);	\
		if (!ASSERT_OK(system(cmd), cmd))		\
			return -1;				\
	})

	SYS("ip netns add ns_dst");
	SYS("ip link add veth1_1 type veth peer name veth2_1 netns ns_dst");
	SYS("ip link add veth1_2 type veth peer name veth2_2 netns ns_dst");

	SYS("ip link add bond1 type bond mode %s xmit_hash_policy %s",
	    mode_names[mode], xmit_policy_names[xmit_policy]);
	SYS("ip link set bond1 up address " BOND1_MAC_STR " addrgenmode none");
	SYS("ip -netns ns_dst link add bond2 type bond mode %s xmit_hash_policy %s",
	    mode_names[mode], xmit_policy_names[xmit_policy]);
	SYS("ip -netns ns_dst link set bond2 up address " BOND2_MAC_STR " addrgenmode none");

	SYS("ip link set veth1_1 master bond1");
	if (bond_both_attach == BOND_BOTH_AND_ATTACH) {
		SYS("ip link set veth1_2 master bond1");
	} else {
		SYS("ip link set veth1_2 up addrgenmode none");

		if (xdp_attach(skeletons, skeletons->xdp_dummy->progs.xdp_dummy_prog, "veth1_2"))
			return -1;
	}

	SYS("ip -netns ns_dst link set veth2_1 master bond2");

	if (bond_both_attach == BOND_BOTH_AND_ATTACH)
		SYS("ip -netns ns_dst link set veth2_2 master bond2");
	else
		SYS("ip -netns ns_dst link set veth2_2 up addrgenmode none");

	/* Load a dummy program on sending side as with veth peer needs to have a
	 * XDP program loaded as well.
	 */
	if (xdp_attach(skeletons, skeletons->xdp_dummy->progs.xdp_dummy_prog, "bond1"))
		return -1;

	if (bond_both_attach == BOND_BOTH_AND_ATTACH) {
		if (!ASSERT_OK(setns_by_name("ns_dst"), "set netns to ns_dst"))
			return -1;

		if (xdp_attach(skeletons, skeletons->xdp_tx->progs.xdp_tx, "bond2"))
			return -1;

		restore_root_netns();
	}

	return 0;

#undef SYS
}

static void bonding_cleanup(struct skeletons *skeletons)
{
	restore_root_netns();
	while (skeletons->nlinks) {
		skeletons->nlinks--;
		bpf_link__destroy(skeletons->links[skeletons->nlinks]);
	}
	ASSERT_OK(system("ip link delete bond1"), "delete bond1");
	ASSERT_OK(system("ip link delete veth1_1"), "delete veth1_1");
	ASSERT_OK(system("ip link delete veth1_2"), "delete veth1_2");
	ASSERT_OK(system("ip netns delete ns_dst"), "delete ns_dst");
}

static int send_udp_packets(int vary_dst_ip)
{
	struct ethhdr eh = {
		.h_source = BOND1_MAC,
		.h_dest = BOND2_MAC,
		.h_proto = htons(ETH_P_IP),
	};
	uint8_t buf[128] = {};
	struct iphdr *iph = (struct iphdr *)(buf + sizeof(eh));
	struct udphdr *uh = (struct udphdr *)(buf + sizeof(eh) + sizeof(*iph));
	int i, s = -1;
	int ifindex;

	s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
	if (!ASSERT_GE(s, 0, "socket"))
		goto err;

	ifindex = if_nametoindex("bond1");
	if (!ASSERT_GT(ifindex, 0, "get bond1 ifindex"))
		goto err;

	memcpy(buf, &eh, sizeof(eh));
	iph->ihl = 5;
	iph->version = 4;
	iph->tos = 16;
	iph->id = 1;
	iph->ttl = 64;
	iph->protocol = IPPROTO_UDP;
	iph->saddr = 1;
	iph->daddr = 2;
	iph->tot_len = htons(sizeof(buf) - ETH_HLEN);
	iph->check = 0;

	for (i = 1; i <= NPACKETS; i++) {
		int n;
		struct sockaddr_ll saddr_ll = {
			.sll_ifindex = ifindex,
			.sll_halen = ETH_ALEN,
			.sll_addr = BOND2_MAC,
		};

		/* vary the UDP destination port for even distribution with roundrobin/xor modes */
		uh->dest++;

		if (vary_dst_ip)
			iph->daddr++;

		n = sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&saddr_ll, sizeof(saddr_ll));
		if (!ASSERT_EQ(n, sizeof(buf), "sendto"))
			goto err;
	}

	return 0;

err:
	if (s >= 0)
		close(s);
	return -1;
}

static void test_xdp_bonding_with_mode(struct skeletons *skeletons, int mode, int xmit_policy)
{
	int bond1_rx;

	if (bonding_setup(skeletons, mode, xmit_policy, BOND_BOTH_AND_ATTACH))
		goto out;

	if (send_udp_packets(xmit_policy != BOND_XMIT_POLICY_LAYER34))
		goto out;

	bond1_rx = get_rx_packets("bond1");
	ASSERT_EQ(bond1_rx, NPACKETS, "expected more received packets");

	switch (mode) {
	case BOND_MODE_ROUNDROBIN:
	case BOND_MODE_XOR: {
		int veth1_rx = get_rx_packets("veth1_1");
		int veth2_rx = get_rx_packets("veth1_2");
		int diff = abs(veth1_rx - veth2_rx);

		ASSERT_GE(veth1_rx + veth2_rx, NPACKETS, "expected more packets");

		switch (xmit_policy) {
		case BOND_XMIT_POLICY_LAYER2:
			ASSERT_GE(diff, NPACKETS,
				  "expected packets on only one of the interfaces");
			break;
		case BOND_XMIT_POLICY_LAYER23:
		case BOND_XMIT_POLICY_LAYER34:
			ASSERT_LT(diff, NPACKETS/2,
				  "expected even distribution of packets");
			break;
		default:
			PRINT_FAIL("Unimplemented xmit_policy=%d\n", xmit_policy);
			break;
		}
		break;
	}
	case BOND_MODE_ACTIVEBACKUP: {
		int veth1_rx = get_rx_packets("veth1_1");
		int veth2_rx = get_rx_packets("veth1_2");
		int diff = abs(veth1_rx - veth2_rx);

		ASSERT_GE(diff, NPACKETS,
			  "expected packets on only one of the interfaces");
		break;
	}
	default:
		PRINT_FAIL("Unimplemented xmit_policy=%d\n", xmit_policy);
		break;
	}

out:
	bonding_cleanup(skeletons);
}

/* Test the broadcast redirection using xdp_redirect_map_multi_prog and adding
 * all the interfaces to it and checking that broadcasting won't send the packet
 * to neither the ingress bond device (bond2) or its slave (veth2_1).
 */
static void test_xdp_bonding_redirect_multi(struct skeletons *skeletons)
{
	static const char * const ifaces[] = {"bond2", "veth2_1", "veth2_2"};
	int veth1_1_rx, veth1_2_rx;
	int err;

	if (bonding_setup(skeletons, BOND_MODE_ROUNDROBIN, BOND_XMIT_POLICY_LAYER23,
			  BOND_ONE_NO_ATTACH))
		goto out;


	if (!ASSERT_OK(setns_by_name("ns_dst"), "could not set netns to ns_dst"))
		goto out;

	/* populate the devmap with the relevant interfaces */
	for (int i = 0; i < ARRAY_SIZE(ifaces); i++) {
		int ifindex = if_nametoindex(ifaces[i]);
		int map_fd = bpf_map__fd(skeletons->xdp_redirect_multi_kern->maps.map_all);

		if (!ASSERT_GT(ifindex, 0, "could not get interface index"))
			goto out;

		err = bpf_map_update_elem(map_fd, &ifindex, &ifindex, 0);
		if (!ASSERT_OK(err, "add interface to map_all"))
			goto out;
	}

	if (xdp_attach(skeletons,
		       skeletons->xdp_redirect_multi_kern->progs.xdp_redirect_map_multi_prog,
		       "bond2"))
		goto out;

	restore_root_netns();

	if (send_udp_packets(BOND_MODE_ROUNDROBIN))
		goto out;

	veth1_1_rx = get_rx_packets("veth1_1");
	veth1_2_rx = get_rx_packets("veth1_2");

	ASSERT_EQ(veth1_1_rx, 0, "expected no packets on veth1_1");
	ASSERT_GE(veth1_2_rx, NPACKETS, "expected packets on veth1_2");

out:
	restore_root_netns();
	bonding_cleanup(skeletons);
}

/* Test that XDP programs cannot be attached to both the bond master and slaves simultaneously */
static void test_xdp_bonding_attach(struct skeletons *skeletons)
{
	struct bpf_link *link = NULL;
	struct bpf_link *link2 = NULL;
	int veth, bond, err;

	if (!ASSERT_OK(system("ip link add veth type veth"), "add veth"))
		goto out;
	if (!ASSERT_OK(system("ip link add bond type bond"), "add bond"))
		goto out;

	veth = if_nametoindex("veth");
	if (!ASSERT_GE(veth, 0, "if_nametoindex veth"))
		goto out;
	bond = if_nametoindex("bond");
	if (!ASSERT_GE(bond, 0, "if_nametoindex bond"))
		goto out;

	/* enslaving with a XDP program loaded is allowed */
	link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth);
	if (!ASSERT_OK_PTR(link, "attach program to veth"))
		goto out;

	err = system("ip link set veth master bond");
	if (!ASSERT_OK(err, "set veth master"))
		goto out;

	bpf_link__destroy(link);
	link = NULL;

	/* attaching to slave when master has no program is allowed */
	link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth);
	if (!ASSERT_OK_PTR(link, "attach program to slave when enslaved"))
		goto out;

	/* attaching to master not allowed when slave has program loaded */
	link2 = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond);
	if (!ASSERT_ERR_PTR(link2, "attach program to master when slave has program"))
		goto out;

	bpf_link__destroy(link);
	link = NULL;

	/* attaching XDP program to master allowed when slave has no program */
	link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond);
	if (!ASSERT_OK_PTR(link, "attach program to master"))
		goto out;

	/* attaching to slave not allowed when master has program loaded */
	link2 = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth);
	if (!ASSERT_ERR_PTR(link2, "attach program to slave when master has program"))
		goto out;

	bpf_link__destroy(link);
	link = NULL;

	/* test program unwinding with a non-XDP slave */
	if (!ASSERT_OK(system("ip link add vxlan type vxlan id 1 remote 1.2.3.4 dstport 0 dev lo"),
		       "add vxlan"))
		goto out;

	err = system("ip link set vxlan master bond");
	if (!ASSERT_OK(err, "set vxlan master"))
		goto out;

	/* attaching not allowed when one slave does not support XDP */
	link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond);
	if (!ASSERT_ERR_PTR(link, "attach program to master when slave does not support XDP"))
		goto out;

out:
	bpf_link__destroy(link);
	bpf_link__destroy(link2);

	system("ip link del veth");
	system("ip link del bond");
	system("ip link del vxlan");
}

/* Test with nested bonding devices to catch issue with negative jump label count */
static void test_xdp_bonding_nested(struct skeletons *skeletons)
{
	struct bpf_link *link = NULL;
	int bond, err;

	if (!ASSERT_OK(system("ip link add bond type bond"), "add bond"))
		goto out;

	bond = if_nametoindex("bond");
	if (!ASSERT_GE(bond, 0, "if_nametoindex bond"))
		goto out;

	if (!ASSERT_OK(system("ip link add bond_nest1 type bond"), "add bond_nest1"))
		goto out;

	err = system("ip link set bond_nest1 master bond");
	if (!ASSERT_OK(err, "set bond_nest1 master"))
		goto out;

	if (!ASSERT_OK(system("ip link add bond_nest2 type bond"), "add bond_nest1"))
		goto out;

	err = system("ip link set bond_nest2 master bond_nest1");
	if (!ASSERT_OK(err, "set bond_nest2 master"))
		goto out;

	link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond);
	ASSERT_OK_PTR(link, "attach program to master");

out:
	bpf_link__destroy(link);
	system("ip link del bond");
	system("ip link del bond_nest1");
	system("ip link del bond_nest2");
}

static int libbpf_debug_print(enum libbpf_print_level level,
			      const char *format, va_list args)
{
	if (level != LIBBPF_WARN)
		vprintf(format, args);
	return 0;
}

struct bond_test_case {
	char *name;
	int mode;
	int xmit_policy;
};

static struct bond_test_case bond_test_cases[] = {
	{ "xdp_bonding_roundrobin", BOND_MODE_ROUNDROBIN, BOND_XMIT_POLICY_LAYER23, },
	{ "xdp_bonding_activebackup", BOND_MODE_ACTIVEBACKUP, BOND_XMIT_POLICY_LAYER23 },

	{ "xdp_bonding_xor_layer2", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER2, },
	{ "xdp_bonding_xor_layer23", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER23, },
	{ "xdp_bonding_xor_layer34", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER34, },
};

void test_xdp_bonding(void)
{
	libbpf_print_fn_t old_print_fn;
	struct skeletons skeletons = {};
	int i;

	old_print_fn = libbpf_set_print(libbpf_debug_print);

	root_netns_fd = open("/proc/self/ns/net", O_RDONLY);
	if (!ASSERT_GE(root_netns_fd, 0, "open /proc/self/ns/net"))
		goto out;

	skeletons.xdp_dummy = xdp_dummy__open_and_load();
	if (!ASSERT_OK_PTR(skeletons.xdp_dummy, "xdp_dummy__open_and_load"))
		goto out;

	skeletons.xdp_tx = xdp_tx__open_and_load();
	if (!ASSERT_OK_PTR(skeletons.xdp_tx, "xdp_tx__open_and_load"))
		goto out;

	skeletons.xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
	if (!ASSERT_OK_PTR(skeletons.xdp_redirect_multi_kern,
			   "xdp_redirect_multi_kern__open_and_load"))
		goto out;

	if (test__start_subtest("xdp_bonding_attach"))
		test_xdp_bonding_attach(&skeletons);

	if (test__start_subtest("xdp_bonding_nested"))
		test_xdp_bonding_nested(&skeletons);

	for (i = 0; i < ARRAY_SIZE(bond_test_cases); i++) {
		struct bond_test_case *test_case = &bond_test_cases[i];

		if (test__start_subtest(test_case->name))
			test_xdp_bonding_with_mode(
				&skeletons,
				test_case->mode,
				test_case->xmit_policy);
	}

	if (test__start_subtest("xdp_bonding_redirect_multi"))
		test_xdp_bonding_redirect_multi(&skeletons);

out:
	xdp_dummy__destroy(skeletons.xdp_dummy);
	xdp_tx__destroy(skeletons.xdp_tx);
	xdp_redirect_multi_kern__destroy(skeletons.xdp_redirect_multi_kern);

	libbpf_set_print(old_print_fn);
	if (root_netns_fd >= 0)
		close(root_netns_fd);
}