1 // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2 
3 /*
4  * Test suite of lwt BPF programs that reroutes 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. This test
7  *   suite currently only covers lwt_xmit hook. lwt_in tests have not been
8  *   implemented.
9  *
10  * WARNING
11  * -------
12  *  This test suite can crash the kernel, thus should be run in a VM.
13  *
14  * Setup:
15  * ---------
16  *  all tests are performed in a single netns. A lwt encap route is setup for
17  *  each subtest:
18  *
19  *    ip route add 10.0.0.0/24 encap bpf xmit <obj> sec "<section_N>" dev link_err
20  *
21  *  Here <obj> is statically defined to test_lwt_reroute.bpf.o, and it contains
22  *  a single test program entry. This program sets packet mark by last byte of
23  *  the IPv4 daddr. For example, a packet going to 1.2.3.4 will receive a skb
24  *  mark 4. A packet will only be marked once, and IP x.x.x.0 will be skipped
25  *  to avoid route loop. We didn't use generated BPF skeleton since the
26  *  attachment for lwt programs are not supported by libbpf yet.
27  *
28  *  The test program will bring up a tun device, and sets up the following
29  *  routes:
30  *
31  *    ip rule add pref 100 from all fwmark <tun_index> lookup 100
32  *    ip route add table 100 default dev tun0
33  *
34  *  For normal testing, a ping command is running in the test netns:
35  *
36  *    ping 10.0.0.<tun_index> -c 1 -w 1 -s 100
37  *
38  *  For abnormal testing, fq is used as the qdisc of the tun device. Then a UDP
39  *  socket will try to overflow the fq queue and trigger qdisc drop error.
40  *
41  * Scenarios:
42  * --------------------------------
43  *  1. Reroute to a running tun device
44  *  2. Reroute to a device where qdisc drop
45  *
46  *  For case 1, ping packets should be received by the tun device.
47  *
48  *  For case 2, force UDP packets to overflow fq limit. As long as kernel
49  *  is not crashed, it is considered successful.
50  */
51 #define NETNS "ns_lwt_reroute"
52 #include <netinet/in.h>
53 #include "lwt_helpers.h"
54 #include "network_helpers.h"
55 #include <linux/net_tstamp.h>
56 
57 #define BPF_OBJECT            "test_lwt_reroute.bpf.o"
58 #define LOCAL_SRC             "10.0.0.1"
59 #define TEST_CIDR             "10.0.0.0/24"
60 #define XMIT_HOOK             "xmit"
61 #define XMIT_SECTION          "lwt_xmit"
62 #define NSEC_PER_SEC          1000000000ULL
63 
64 /* send a ping to be rerouted to the target device */
ping_once(const char * ip)65 static void ping_once(const char *ip)
66 {
67 	/* We won't get a reply. Don't fail here */
68 	SYS_NOFAIL("ping %s -c1 -W1 -s %d",
69 		   ip, ICMP_PAYLOAD_SIZE);
70 }
71 
72 /* Send snd_target UDP packets to overflow the fq queue and trigger qdisc drop
73  * error. This is done via TX tstamp to force buffering delayed packets.
74  */
overflow_fq(int snd_target,const char * target_ip)75 static int overflow_fq(int snd_target, const char *target_ip)
76 {
77 	struct sockaddr_in addr = {
78 		.sin_family = AF_INET,
79 		.sin_port = htons(1234),
80 	};
81 
82 	char data_buf[8]; /* only #pkts matter, so use a random small buffer */
83 	char control_buf[CMSG_SPACE(sizeof(uint64_t))];
84 	struct iovec iov = {
85 		.iov_base = data_buf,
86 		.iov_len = sizeof(data_buf),
87 	};
88 	int err = -1;
89 	int s = -1;
90 	struct sock_txtime txtime_on = {
91 		.clockid = CLOCK_MONOTONIC,
92 		.flags = 0,
93 	};
94 	struct msghdr msg = {
95 		.msg_name = &addr,
96 		.msg_namelen = sizeof(addr),
97 		.msg_control = control_buf,
98 		.msg_controllen = sizeof(control_buf),
99 		.msg_iovlen = 1,
100 		.msg_iov = &iov,
101 	};
102 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
103 
104 	memset(data_buf, 0, sizeof(data_buf));
105 
106 	s = socket(AF_INET, SOCK_DGRAM, 0);
107 	if (!ASSERT_GE(s, 0, "socket"))
108 		goto out;
109 
110 	err = setsockopt(s, SOL_SOCKET, SO_TXTIME, &txtime_on, sizeof(txtime_on));
111 	if (!ASSERT_OK(err, "setsockopt(SO_TXTIME)"))
112 		goto out;
113 
114 	err = inet_pton(AF_INET, target_ip, &addr.sin_addr);
115 	if (!ASSERT_EQ(err, 1, "inet_pton"))
116 		goto out;
117 
118 	while (snd_target > 0) {
119 		struct timespec now;
120 
121 		memset(control_buf, 0, sizeof(control_buf));
122 		cmsg->cmsg_type = SCM_TXTIME;
123 		cmsg->cmsg_level = SOL_SOCKET;
124 		cmsg->cmsg_len = CMSG_LEN(sizeof(uint64_t));
125 
126 		err = clock_gettime(CLOCK_MONOTONIC, &now);
127 		if (!ASSERT_OK(err, "clock_gettime(CLOCK_MONOTONIC)")) {
128 			err = -1;
129 			goto out;
130 		}
131 
132 		*(uint64_t *)CMSG_DATA(cmsg) = (now.tv_nsec + 1) * NSEC_PER_SEC +
133 					       now.tv_nsec;
134 
135 		/* we will intentionally send more than fq limit, so ignore
136 		 * the error here.
137 		 */
138 		sendmsg(s, &msg, MSG_NOSIGNAL);
139 		snd_target--;
140 	}
141 
142 	/* no kernel crash so far is considered success */
143 	err = 0;
144 
145 out:
146 	if (s >= 0)
147 		close(s);
148 
149 	return err;
150 }
151 
setup(const char * tun_dev)152 static int setup(const char *tun_dev)
153 {
154 	int target_index = -1;
155 	int tap_fd = -1;
156 
157 	tap_fd = open_tuntap(tun_dev, false);
158 	if (!ASSERT_GE(tap_fd, 0, "open_tun"))
159 		return -1;
160 
161 	target_index = if_nametoindex(tun_dev);
162 	if (!ASSERT_GE(target_index, 0, "if_nametoindex"))
163 		return -1;
164 
165 	SYS(fail, "ip link add link_err type dummy");
166 	SYS(fail, "ip link set lo up");
167 	SYS(fail, "ip addr add dev lo " LOCAL_SRC "/32");
168 	SYS(fail, "ip link set link_err up");
169 	SYS(fail, "ip link set %s up", tun_dev);
170 
171 	SYS(fail, "ip route add %s dev link_err encap bpf xmit obj %s sec lwt_xmit",
172 	    TEST_CIDR, BPF_OBJECT);
173 
174 	SYS(fail, "ip rule add pref 100 from all fwmark %d lookup 100",
175 	    target_index);
176 	SYS(fail, "ip route add t 100 default dev %s", tun_dev);
177 
178 	return tap_fd;
179 
180 fail:
181 	if (tap_fd >= 0)
182 		close(tap_fd);
183 	return -1;
184 }
185 
test_lwt_reroute_normal_xmit(void)186 static void test_lwt_reroute_normal_xmit(void)
187 {
188 	const char *tun_dev = "tun0";
189 	int tun_fd = -1;
190 	int ifindex = -1;
191 	char ip[256];
192 	struct timeval timeo = {
193 		.tv_sec = 0,
194 		.tv_usec = 250000,
195 	};
196 
197 	tun_fd = setup(tun_dev);
198 	if (!ASSERT_GE(tun_fd, 0, "setup_reroute"))
199 		return;
200 
201 	ifindex = if_nametoindex(tun_dev);
202 	if (!ASSERT_GE(ifindex, 0, "if_nametoindex"))
203 		return;
204 
205 	snprintf(ip, 256, "10.0.0.%d", ifindex);
206 
207 	/* ping packets should be received by the tun device */
208 	ping_once(ip);
209 
210 	if (!ASSERT_EQ(wait_for_packet(tun_fd, __expect_icmp_ipv4, &timeo), 1,
211 		       "wait_for_packet"))
212 		log_err("%s xmit", __func__);
213 }
214 
215 /*
216  * Test the failure case when the skb is dropped at the qdisc. This is a
217  * regression prevention at the xmit hook only.
218  */
test_lwt_reroute_qdisc_dropped(void)219 static void test_lwt_reroute_qdisc_dropped(void)
220 {
221 	const char *tun_dev = "tun0";
222 	int tun_fd = -1;
223 	int ifindex = -1;
224 	char ip[256];
225 
226 	tun_fd = setup(tun_dev);
227 	if (!ASSERT_GE(tun_fd, 0, "setup_reroute"))
228 		goto fail;
229 
230 	SYS(fail, "tc qdisc replace dev %s root fq limit 5 flow_limit 5", tun_dev);
231 
232 	ifindex = if_nametoindex(tun_dev);
233 	if (!ASSERT_GE(ifindex, 0, "if_nametoindex"))
234 		return;
235 
236 	snprintf(ip, 256, "10.0.0.%d", ifindex);
237 	ASSERT_EQ(overflow_fq(10, ip), 0, "overflow_fq");
238 
239 fail:
240 	if (tun_fd >= 0)
241 		close(tun_fd);
242 }
243 
test_lwt_reroute_run(void * arg)244 static void *test_lwt_reroute_run(void *arg)
245 {
246 	netns_delete();
247 	RUN_TEST(lwt_reroute_normal_xmit);
248 	RUN_TEST(lwt_reroute_qdisc_dropped);
249 	return NULL;
250 }
251 
test_lwt_reroute(void)252 void test_lwt_reroute(void)
253 {
254 	pthread_t test_thread;
255 	int err;
256 
257 	/* Run the tests in their own thread to isolate the namespace changes
258 	 * so they do not affect the environment of other tests.
259 	 * (specifically needed because of unshare(CLONE_NEWNS) in open_netns())
260 	 */
261 	err = pthread_create(&test_thread, NULL, &test_lwt_reroute_run, NULL);
262 	if (ASSERT_OK(err, "pthread_create"))
263 		ASSERT_OK(pthread_join(test_thread, NULL), "pthread_join");
264 }
265