265 lines · c
1// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause2 3/*4 * Test suite of lwt BPF programs that reroutes packets5 * The file tests focus not only if these programs work as expected normally,6 * but also if they can handle abnormal situations gracefully. This test7 * suite currently only covers lwt_xmit hook. lwt_in tests have not been8 * implemented.9 *10 * WARNING11 * -------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 for17 * each subtest:18 *19 * ip route add 10.0.0.0/24 encap bpf xmit <obj> sec "<section_N>" dev link_err20 *21 * Here <obj> is statically defined to test_lwt_reroute.bpf.o, and it contains22 * a single test program entry. This program sets packet mark by last byte of23 * the IPv4 daddr. For example, a packet going to 1.2.3.4 will receive a skb24 * mark 4. A packet will only be marked once, and IP x.x.x.0 will be skipped25 * to avoid route loop. We didn't use generated BPF skeleton since the26 * 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 following29 * routes:30 *31 * ip rule add pref 100 from all fwmark <tun_index> lookup 10032 * ip route add table 100 default dev tun033 *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 10037 *38 * For abnormal testing, fq is used as the qdisc of the tun device. Then a UDP39 * socket will try to overflow the fq queue and trigger qdisc drop error.40 *41 * Scenarios:42 * --------------------------------43 * 1. Reroute to a running tun device44 * 2. Reroute to a device where qdisc drop45 *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 kernel49 * 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 1000000000ULL63 64/* send a ping to be rerouted to the target device */65static 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 drop73 * error. This is done via TX tstamp to force buffering delayed packets.74 */75static 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 ignore136 * 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 145out:146 if (s >= 0)147 close(s);148 149 return err;150}151 152static 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 180fail:181 if (tap_fd >= 0)182 close(tap_fd);183 return -1;184}185 186static 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 a217 * regression prevention at the xmit hook only.218 */219static 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 239fail:240 if (tun_fd >= 0)241 close(tun_fd);242}243 244static 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 252void 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 changes258 * 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