aboutsummaryrefslogtreecommitdiffstats
path: root/tools/testing/selftests/bpf/prog_tests/xdp_do_redirect.c
blob: a50971c6cf4a5d4f8efdae804199c51142ca8170 (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
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include <network_helpers.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/ipv6.h>
#include <linux/in6.h>
#include <linux/udp.h>
#include <bpf/bpf_endian.h>
#include "test_xdp_do_redirect.skel.h"

#define SYS(fmt, ...)						\
	({							\
		char cmd[1024];					\
		snprintf(cmd, sizeof(cmd), fmt, ##__VA_ARGS__);	\
		if (!ASSERT_OK(system(cmd), cmd))		\
			goto out;				\
	})

struct udp_packet {
	struct ethhdr eth;
	struct ipv6hdr iph;
	struct udphdr udp;
	__u8 payload[64 - sizeof(struct udphdr)
		     - sizeof(struct ethhdr) - sizeof(struct ipv6hdr)];
} __packed;

static struct udp_packet pkt_udp = {
	.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
	.eth.h_dest = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55},
	.eth.h_source = {0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb},
	.iph.version = 6,
	.iph.nexthdr = IPPROTO_UDP,
	.iph.payload_len = bpf_htons(sizeof(struct udp_packet)
				     - offsetof(struct udp_packet, udp)),
	.iph.hop_limit = 2,
	.iph.saddr.s6_addr16 = {bpf_htons(0xfc00), 0, 0, 0, 0, 0, 0, bpf_htons(1)},
	.iph.daddr.s6_addr16 = {bpf_htons(0xfc00), 0, 0, 0, 0, 0, 0, bpf_htons(2)},
	.udp.source = bpf_htons(1),
	.udp.dest = bpf_htons(1),
	.udp.len = bpf_htons(sizeof(struct udp_packet)
			     - offsetof(struct udp_packet, udp)),
	.payload = {0x42}, /* receiver XDP program matches on this */
};

static int attach_tc_prog(struct bpf_tc_hook *hook, int fd)
{
	DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, .prog_fd = fd);
	int ret;

	ret = bpf_tc_hook_create(hook);
	if (!ASSERT_OK(ret, "create tc hook"))
		return ret;

	ret = bpf_tc_attach(hook, &opts);
	if (!ASSERT_OK(ret, "bpf_tc_attach")) {
		bpf_tc_hook_destroy(hook);
		return ret;
	}

	return 0;
}

/* The maximum permissible size is: PAGE_SIZE - sizeof(struct xdp_page_head) -
 * sizeof(struct skb_shared_info) - XDP_PACKET_HEADROOM = 3368 bytes
 */
#define MAX_PKT_SIZE 3368
static void test_max_pkt_size(int fd)
{
	char data[MAX_PKT_SIZE + 1] = {};
	int err;
	DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts,
			    .data_in = &data,
			    .data_size_in = MAX_PKT_SIZE,
			    .flags = BPF_F_TEST_XDP_LIVE_FRAMES,
			    .repeat = 1,
		);
	err = bpf_prog_test_run_opts(fd, &opts);
	ASSERT_OK(err, "prog_run_max_size");

	opts.data_size_in += 1;
	err = bpf_prog_test_run_opts(fd, &opts);
	ASSERT_EQ(err, -EINVAL, "prog_run_too_big");
}

#define NUM_PKTS 10000
void test_xdp_do_redirect(void)
{
	int err, xdp_prog_fd, tc_prog_fd, ifindex_src, ifindex_dst;
	char data[sizeof(pkt_udp) + sizeof(__u32)];
	struct test_xdp_do_redirect *skel = NULL;
	struct nstoken *nstoken = NULL;
	struct bpf_link *link;

	struct xdp_md ctx_in = { .data = sizeof(__u32),
				 .data_end = sizeof(data) };
	DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts,
			    .data_in = &data,
			    .data_size_in = sizeof(data),
			    .ctx_in = &ctx_in,
			    .ctx_size_in = sizeof(ctx_in),
			    .flags = BPF_F_TEST_XDP_LIVE_FRAMES,
			    .repeat = NUM_PKTS,
			    .batch_size = 64,
		);
	DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook,
			    .attach_point = BPF_TC_INGRESS);

	memcpy(&data[sizeof(__u32)], &pkt_udp, sizeof(pkt_udp));
	*((__u32 *)data) = 0x42; /* metadata test value */

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

	/* The XDP program we run with bpf_prog_run() will cycle through all
	 * three xmit (PASS/TX/REDIRECT) return codes starting from above, and
	 * ending up with PASS, so we should end up with two packets on the dst
	 * iface and NUM_PKTS-2 in the TC hook. We match the packets on the UDP
	 * payload.
	 */
	SYS("ip netns add testns");
	nstoken = open_netns("testns");
	if (!ASSERT_OK_PTR(nstoken, "setns"))
		goto out;

	SYS("ip link add veth_src type veth peer name veth_dst");
	SYS("ip link set dev veth_src address 00:11:22:33:44:55");
	SYS("ip link set dev veth_dst address 66:77:88:99:aa:bb");
	SYS("ip link set dev veth_src up");
	SYS("ip link set dev veth_dst up");
	SYS("ip addr add dev veth_src fc00::1/64");
	SYS("ip addr add dev veth_dst fc00::2/64");
	SYS("ip neigh add fc00::2 dev veth_src lladdr 66:77:88:99:aa:bb");

	/* We enable forwarding in the test namespace because that will cause
	 * the packets that go through the kernel stack (with XDP_PASS) to be
	 * forwarded back out the same interface (because of the packet dst
	 * combined with the interface addresses). When this happens, the
	 * regular forwarding path will end up going through the same
	 * veth_xdp_xmit() call as the XDP_REDIRECT code, which can cause a
	 * deadlock if it happens on the same CPU. There's a local_bh_disable()
	 * in the test_run code to prevent this, but an earlier version of the
	 * code didn't have this, so we keep the test behaviour to make sure the
	 * bug doesn't resurface.
	 */
	SYS("sysctl -qw net.ipv6.conf.all.forwarding=1");

	ifindex_src = if_nametoindex("veth_src");
	ifindex_dst = if_nametoindex("veth_dst");
	if (!ASSERT_NEQ(ifindex_src, 0, "ifindex_src") ||
	    !ASSERT_NEQ(ifindex_dst, 0, "ifindex_dst"))
		goto out;

	memcpy(skel->rodata->expect_dst, &pkt_udp.eth.h_dest, ETH_ALEN);
	skel->rodata->ifindex_out = ifindex_src; /* redirect back to the same iface */
	skel->rodata->ifindex_in = ifindex_src;
	ctx_in.ingress_ifindex = ifindex_src;
	tc_hook.ifindex = ifindex_src;

	if (!ASSERT_OK(test_xdp_do_redirect__load(skel), "load"))
		goto out;

	link = bpf_program__attach_xdp(skel->progs.xdp_count_pkts, ifindex_dst);
	if (!ASSERT_OK_PTR(link, "prog_attach"))
		goto out;
	skel->links.xdp_count_pkts = link;

	tc_prog_fd = bpf_program__fd(skel->progs.tc_count_pkts);
	if (attach_tc_prog(&tc_hook, tc_prog_fd))
		goto out;

	xdp_prog_fd = bpf_program__fd(skel->progs.xdp_redirect);
	err = bpf_prog_test_run_opts(xdp_prog_fd, &opts);
	if (!ASSERT_OK(err, "prog_run"))
		goto out_tc;

	/* wait for the packets to be flushed */
	kern_sync_rcu();

	/* There will be one packet sent through XDP_REDIRECT and one through
	 * XDP_TX; these will show up on the XDP counting program, while the
	 * rest will be counted at the TC ingress hook (and the counting program
	 * resets the packet payload so they don't get counted twice even though
	 * they are re-xmited out the veth device
	 */
	ASSERT_EQ(skel->bss->pkts_seen_xdp, 2, "pkt_count_xdp");
	ASSERT_EQ(skel->bss->pkts_seen_zero, 2, "pkt_count_zero");
	ASSERT_EQ(skel->bss->pkts_seen_tc, NUM_PKTS - 2, "pkt_count_tc");

	test_max_pkt_size(bpf_program__fd(skel->progs.xdp_count_pkts));

out_tc:
	bpf_tc_hook_destroy(&tc_hook);
out:
	if (nstoken)
		close_netns(nstoken);
	system("ip netns del testns");
	test_xdp_do_redirect__destroy(skel);
}