File Index
Files and directories
Sign in
FILE

lwt_redirect.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/bpf/prog_tests/lwt_redirect.c C
  1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2
  3/*
  4 * Test suite of lwt_xmit BPF programs that redirect packets
  5 *   The file tests focus not only if these programs work as expected normally,
  6 *   but also if they can handle abnormal situations gracefully.
  7 *
  8 * WARNING
  9 * -------
 10 *  This test suite may crash the kernel, thus should be run in a VM.
 11 *
 12 * Setup:
 13 * ---------
 14 *  All tests are performed in a single netns. Two lwt encap routes are setup for
 15 *  each subtest:
 16 *
 17 *    ip route add 10.0.0.0/24 encap bpf xmit <obj> sec "<ingress_sec>" dev link_err
 18 *    ip route add 20.0.0.0/24 encap bpf xmit <obj> sec "<egress_sec>" dev link_err
 19 *
 20 *  Here <obj> is statically defined to test_lwt_redirect.bpf.o, and each section
 21 *  of this object holds a program entry to test. The BPF object is built from
 22 *  progs/test_lwt_redirect.c. We didn't use generated BPF skeleton since the
 23 *  attachment for lwt programs are not supported by libbpf yet.
 24 *
 25 *  For testing, ping commands are run in the test netns:
 26 *
 27 *    ping 10.0.0.<ifindex> -c 1 -w 1 -s 100
 28 *    ping 20.0.0.<ifindex> -c 1 -w 1 -s 100
 29 *
 30 * Scenarios:
 31 * --------------------------------
 32 *  1. Redirect to a running tap/tun device
 33 *  2. Redirect to a down tap/tun device
 34 *  3. Redirect to a vlan device with lower layer down
 35 *
 36 *  Case 1, ping packets should be received by packet socket on target device
 37 *  when redirected to ingress, and by tun/tap fd when redirected to egress.
 38 *
 39 *  Case 2,3 are considered successful as long as they do not crash the kernel
 40 *  as a regression.
 41 *
 42 *  Case 1,2 use tap device to test redirect to device that requires MAC
 43 *  header, and tun device to test the case with no MAC header added.
 44 */
 45#include <sys/socket.h>
 46#include <net/if.h>
 47#include <linux/if_ether.h>
 48#include <linux/if_packet.h>
 49#include <linux/if_tun.h>
 50#include <arpa/inet.h>
 51#include <unistd.h>
 52#include <errno.h>
 53#include <stdbool.h>
 54#include <stdlib.h>
 55
 56#define NETNS "ns_lwt_redirect"
 57#include "lwt_helpers.h"
 58#include "test_progs.h"
 59#include "network_helpers.h"
 60
 61#define BPF_OBJECT            "test_lwt_redirect.bpf.o"
 62#define INGRESS_SEC(need_mac) ((need_mac) ? "redir_ingress" : "redir_ingress_nomac")
 63#define EGRESS_SEC(need_mac)  ((need_mac) ? "redir_egress" : "redir_egress_nomac")
 64#define LOCAL_SRC             "10.0.0.1"
 65#define CIDR_TO_INGRESS       "10.0.0.0/24"
 66#define CIDR_TO_EGRESS        "20.0.0.0/24"
 67
 68/* ping to redirect toward given dev, with last byte of dest IP being the target
 69 * device index.
 70 *
 71 * Note: ping command inside BPF-CI is busybox version, so it does not have certain
 72 * function, such like -m option to set packet mark.
 73 */
 74static void ping_dev(const char *dev, bool is_ingress)
 75{
 76	int link_index = if_nametoindex(dev);
 77	char ip[256];
 78
 79	if (!ASSERT_GE(link_index, 0, "if_nametoindex"))
 80		return;
 81
 82	if (is_ingress)
 83		snprintf(ip, sizeof(ip), "10.0.0.%d", link_index);
 84	else
 85		snprintf(ip, sizeof(ip), "20.0.0.%d", link_index);
 86
 87	/* We won't get a reply. Don't fail here */
 88	SYS_NOFAIL("ping %s -c1 -W1 -s %d",
 89		   ip, ICMP_PAYLOAD_SIZE);
 90}
 91
 92static int new_packet_sock(const char *ifname)
 93{
 94	int err = 0;
 95	int ignore_outgoing = 1;
 96	int ifindex = -1;
 97	int s = -1;
 98
 99	s = socket(AF_PACKET, SOCK_RAW, 0);
100	if (!ASSERT_GE(s, 0, "socket(AF_PACKET)"))
101		return -1;
102
103	ifindex = if_nametoindex(ifname);
104	if (!ASSERT_GE(ifindex, 0, "if_nametoindex")) {
105		close(s);
106		return -1;
107	}
108
109	struct sockaddr_ll addr = {
110		.sll_family = AF_PACKET,
111		.sll_protocol = htons(ETH_P_IP),
112		.sll_ifindex = ifindex,
113	};
114
115	err = bind(s, (struct sockaddr *)&addr, sizeof(addr));
116	if (!ASSERT_OK(err, "bind(AF_PACKET)")) {
117		close(s);
118		return -1;
119	}
120
121	/* Use packet socket to capture only the ingress, so we can distinguish
122	 * the case where a regression that actually redirects the packet to
123	 * the egress.
124	 */
125	err = setsockopt(s, SOL_PACKET, PACKET_IGNORE_OUTGOING,
126			 &ignore_outgoing, sizeof(ignore_outgoing));
127	if (!ASSERT_OK(err, "setsockopt(PACKET_IGNORE_OUTGOING)")) {
128		close(s);
129		return -1;
130	}
131
132	err = fcntl(s, F_SETFL, O_NONBLOCK);
133	if (!ASSERT_OK(err, "fcntl(O_NONBLOCK)")) {
134		close(s);
135		return -1;
136	}
137
138	return s;
139}
140
141static int expect_icmp(char *buf, ssize_t len)
142{
143	struct ethhdr *eth = (struct ethhdr *)buf;
144
145	if (len < (ssize_t)sizeof(*eth))
146		return -1;
147
148	if (eth->h_proto == htons(ETH_P_IP))
149		return __expect_icmp_ipv4((char *)(eth + 1), len - sizeof(*eth));
150
151	return -1;
152}
153
154static int expect_icmp_nomac(char *buf, ssize_t len)
155{
156	return __expect_icmp_ipv4(buf, len);
157}
158
159static void send_and_capture_test_packets(const char *test_name, int tap_fd,
160					  const char *target_dev, bool need_mac)
161{
162	int psock = -1;
163	struct timeval timeo = {
164		.tv_sec = 0,
165		.tv_usec = 250000,
166	};
167	int ret = -1;
168
169	filter_t filter = need_mac ? expect_icmp : expect_icmp_nomac;
170
171	ping_dev(target_dev, false);
172
173	ret = wait_for_packet(tap_fd, filter, &timeo);
174	if (!ASSERT_EQ(ret, 1, "wait_for_epacket")) {
175		log_err("%s egress test fails", test_name);
176		goto out;
177	}
178
179	psock = new_packet_sock(target_dev);
180	ping_dev(target_dev, true);
181
182	ret = wait_for_packet(psock, filter, &timeo);
183	if (!ASSERT_EQ(ret, 1, "wait_for_ipacket")) {
184		log_err("%s ingress test fails", test_name);
185		goto out;
186	}
187
188out:
189	if (psock >= 0)
190		close(psock);
191}
192
193static int setup_redirect_target(const char *target_dev, bool need_mac)
194{
195	int target_index = -1;
196	int tap_fd = -1;
197
198	tap_fd = open_tuntap(target_dev, need_mac);
199	if (!ASSERT_GE(tap_fd, 0, "open_tuntap"))
200		goto fail;
201
202	target_index = if_nametoindex(target_dev);
203	if (!ASSERT_GE(target_index, 0, "if_nametoindex"))
204		goto fail;
205
206	SYS(fail, "sysctl -w net.ipv6.conf.all.disable_ipv6=1");
207	SYS(fail, "ip link add link_err type dummy");
208	SYS(fail, "ip link set lo up");
209	SYS(fail, "ip addr add dev lo " LOCAL_SRC "/32");
210	SYS(fail, "ip link set link_err up");
211	SYS(fail, "ip link set %s up", target_dev);
212
213	SYS(fail, "ip route add %s dev link_err encap bpf xmit obj %s sec %s",
214	    CIDR_TO_INGRESS, BPF_OBJECT, INGRESS_SEC(need_mac));
215
216	SYS(fail, "ip route add %s dev link_err encap bpf xmit obj %s sec %s",
217	    CIDR_TO_EGRESS, BPF_OBJECT, EGRESS_SEC(need_mac));
218
219	return tap_fd;
220
221fail:
222	if (tap_fd >= 0)
223		close(tap_fd);
224	return -1;
225}
226
227static void test_lwt_redirect_normal(void)
228{
229	const char *target_dev = "tap0";
230	int tap_fd = -1;
231	bool need_mac = true;
232
233	tap_fd = setup_redirect_target(target_dev, need_mac);
234	if (!ASSERT_GE(tap_fd, 0, "setup_redirect_target"))
235		return;
236
237	send_and_capture_test_packets(__func__, tap_fd, target_dev, need_mac);
238	close(tap_fd);
239}
240
241static void test_lwt_redirect_normal_nomac(void)
242{
243	const char *target_dev = "tun0";
244	int tap_fd = -1;
245	bool need_mac = false;
246
247	tap_fd = setup_redirect_target(target_dev, need_mac);
248	if (!ASSERT_GE(tap_fd, 0, "setup_redirect_target"))
249		return;
250
251	send_and_capture_test_packets(__func__, tap_fd, target_dev, need_mac);
252	close(tap_fd);
253}
254
255/* This test aims to prevent regression of future. As long as the kernel does
256 * not panic, it is considered as success.
257 */
258static void __test_lwt_redirect_dev_down(bool need_mac)
259{
260	const char *target_dev = "tap0";
261	int tap_fd = -1;
262
263	tap_fd = setup_redirect_target(target_dev, need_mac);
264	if (!ASSERT_GE(tap_fd, 0, "setup_redirect_target"))
265		return;
266
267	SYS(out, "ip link set %s down", target_dev);
268	ping_dev(target_dev, true);
269	ping_dev(target_dev, false);
270
271out:
272	close(tap_fd);
273}
274
275static void test_lwt_redirect_dev_down(void)
276{
277	__test_lwt_redirect_dev_down(true);
278}
279
280static void test_lwt_redirect_dev_down_nomac(void)
281{
282	__test_lwt_redirect_dev_down(false);
283}
284
285/* This test aims to prevent regression of future. As long as the kernel does
286 * not panic, it is considered as success.
287 */
288static void test_lwt_redirect_dev_carrier_down(void)
289{
290	const char *lower_dev = "tap0";
291	const char *vlan_dev = "vlan100";
292	int tap_fd = -1;
293
294	tap_fd = setup_redirect_target(lower_dev, true);
295	if (!ASSERT_GE(tap_fd, 0, "setup_redirect_target"))
296		return;
297
298	SYS(out, "ip link add vlan100 link %s type vlan id 100", lower_dev);
299	SYS(out, "ip link set %s up", vlan_dev);
300	SYS(out, "ip link set %s down", lower_dev);
301	ping_dev(vlan_dev, true);
302	ping_dev(vlan_dev, false);
303
304out:
305	close(tap_fd);
306}
307
308static void *test_lwt_redirect_run(void *arg)
309{
310	netns_delete();
311	RUN_TEST(lwt_redirect_normal);
312	RUN_TEST(lwt_redirect_normal_nomac);
313	RUN_TEST(lwt_redirect_dev_down);
314	RUN_TEST(lwt_redirect_dev_down_nomac);
315	RUN_TEST(lwt_redirect_dev_carrier_down);
316	return NULL;
317}
318
319void test_lwt_redirect(void)
320{
321	pthread_t test_thread;
322	int err;
323
324	/* Run the tests in their own thread to isolate the namespace changes
325	 * so they do not affect the environment of other tests.
326	 * (specifically needed because of unshare(CLONE_NEWNS) in open_netns())
327	 */
328	err = pthread_create(&test_thread, NULL, &test_lwt_redirect_run, NULL);
329	if (ASSERT_OK(err, "pthread_create"))
330		ASSERT_OK(pthread_join(test_thread, NULL), "pthread_join");
331}
~/ Read-only file preview.