1096eccdeSJussi Maki // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
2096eccdeSJussi Maki 
3096eccdeSJussi Maki /*
4096eccdeSJussi Maki  * This test sets up 3 netns (src <-> fwd <-> dst). There is no direct veth link
5096eccdeSJussi Maki  * between src and dst. The netns fwd has veth links to each src and dst. The
6096eccdeSJussi Maki  * client is in src and server in dst. The test installs a TC BPF program to each
7096eccdeSJussi Maki  * host facing veth in fwd which calls into i) bpf_redirect_neigh() to perform the
8096eccdeSJussi Maki  * neigh addr population and redirect or ii) bpf_redirect_peer() for namespace
9096eccdeSJussi Maki  * switch from ingress side; it also installs a checker prog on the egress side
10096eccdeSJussi Maki  * to drop unexpected traffic.
11096eccdeSJussi Maki  */
12096eccdeSJussi Maki 
136fd5fb63SJussi Maki #include <arpa/inet.h>
144cda0c82SJussi Maki #include <linux/if_tun.h>
15096eccdeSJussi Maki #include <linux/limits.h>
16096eccdeSJussi Maki #include <linux/sysctl.h>
17c803475fSMartin KaFai Lau #include <linux/time_types.h>
18c803475fSMartin KaFai Lau #include <linux/net_tstamp.h>
19052c82dcSMartin KaFai Lau #include <net/if.h>
20096eccdeSJussi Maki #include <stdbool.h>
21096eccdeSJussi Maki #include <stdio.h>
224cda0c82SJussi Maki #include <sys/stat.h>
234cda0c82SJussi Maki #include <unistd.h>
24096eccdeSJussi Maki 
25096eccdeSJussi Maki #include "test_progs.h"
26096eccdeSJussi Maki #include "network_helpers.h"
27096eccdeSJussi Maki #include "test_tc_neigh_fib.skel.h"
28096eccdeSJussi Maki #include "test_tc_neigh.skel.h"
29096eccdeSJussi Maki #include "test_tc_peer.skel.h"
30c803475fSMartin KaFai Lau #include "test_tc_dtime.skel.h"
31c803475fSMartin KaFai Lau 
32c803475fSMartin KaFai Lau #ifndef TCP_TX_DELAY
33c803475fSMartin KaFai Lau #define TCP_TX_DELAY 37
34c803475fSMartin KaFai Lau #endif
35096eccdeSJussi Maki 
36096eccdeSJussi Maki #define NS_SRC "ns_src"
37096eccdeSJussi Maki #define NS_FWD "ns_fwd"
38096eccdeSJussi Maki #define NS_DST "ns_dst"
39096eccdeSJussi Maki 
40096eccdeSJussi Maki #define IP4_SRC "172.16.1.100"
41096eccdeSJussi Maki #define IP4_DST "172.16.2.100"
426fd5fb63SJussi Maki #define IP4_TUN_SRC "172.17.1.100"
436fd5fb63SJussi Maki #define IP4_TUN_FWD "172.17.1.200"
44096eccdeSJussi Maki #define IP4_PORT 9004
45096eccdeSJussi Maki 
466fd5fb63SJussi Maki #define IP6_SRC "0::1:dead:beef:cafe"
476fd5fb63SJussi Maki #define IP6_DST "0::2:dead:beef:cafe"
486fd5fb63SJussi Maki #define IP6_TUN_SRC "1::1:dead:beef:cafe"
496fd5fb63SJussi Maki #define IP6_TUN_FWD "1::2:dead:beef:cafe"
50096eccdeSJussi Maki #define IP6_PORT 9006
51096eccdeSJussi Maki 
52096eccdeSJussi Maki #define IP4_SLL "169.254.0.1"
53096eccdeSJussi Maki #define IP4_DLL "169.254.0.2"
54096eccdeSJussi Maki #define IP4_NET "169.254.0.0"
55096eccdeSJussi Maki 
566fd5fb63SJussi Maki #define MAC_DST_FWD "00:11:22:33:44:55"
576fd5fb63SJussi Maki #define MAC_DST "00:22:33:44:55:66"
586fd5fb63SJussi Maki 
59096eccdeSJussi Maki #define IFADDR_STR_LEN 18
606fd5fb63SJussi Maki #define PING_ARGS "-i 0.2 -c 3 -w 10 -q"
61096eccdeSJussi Maki 
62096eccdeSJussi Maki #define TIMEOUT_MILLIS 10000
63c803475fSMartin KaFai Lau #define NSEC_PER_SEC 1000000000ULL
64096eccdeSJussi Maki 
65096eccdeSJussi Maki #define log_err(MSG, ...) \
66096eccdeSJussi Maki 	fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \
67096eccdeSJussi Maki 		__FILE__, __LINE__, strerror(errno), ##__VA_ARGS__)
68096eccdeSJussi Maki 
69096eccdeSJussi Maki static const char * const namespaces[] = {NS_SRC, NS_FWD, NS_DST, NULL};
70096eccdeSJussi Maki 
write_file(const char * path,const char * newval)716fd5fb63SJussi Maki static int write_file(const char *path, const char *newval)
72096eccdeSJussi Maki {
73096eccdeSJussi Maki 	FILE *f;
74096eccdeSJussi Maki 
75096eccdeSJussi Maki 	f = fopen(path, "r+");
76096eccdeSJussi Maki 	if (!f)
77096eccdeSJussi Maki 		return -1;
78096eccdeSJussi Maki 	if (fwrite(newval, strlen(newval), 1, f) != 1) {
79096eccdeSJussi Maki 		log_err("writing to %s failed", path);
80096eccdeSJussi Maki 		fclose(f);
81096eccdeSJussi Maki 		return -1;
82096eccdeSJussi Maki 	}
83096eccdeSJussi Maki 	fclose(f);
846fd5fb63SJussi Maki 	return 0;
85096eccdeSJussi Maki }
86096eccdeSJussi Maki 
netns_setup_namespaces(const char * verb)87096eccdeSJussi Maki static int netns_setup_namespaces(const char *verb)
88096eccdeSJussi Maki {
89096eccdeSJussi Maki 	const char * const *ns = namespaces;
90096eccdeSJussi Maki 	char cmd[128];
91096eccdeSJussi Maki 
92096eccdeSJussi Maki 	while (*ns) {
93096eccdeSJussi Maki 		snprintf(cmd, sizeof(cmd), "ip netns %s %s", verb, *ns);
94096eccdeSJussi Maki 		if (!ASSERT_OK(system(cmd), cmd))
95096eccdeSJussi Maki 			return -1;
96096eccdeSJussi Maki 		ns++;
97096eccdeSJussi Maki 	}
98096eccdeSJussi Maki 	return 0;
99096eccdeSJussi Maki }
100096eccdeSJussi Maki 
netns_setup_namespaces_nofail(const char * verb)101e1ef62a4SYucong Sun static void netns_setup_namespaces_nofail(const char *verb)
102e1ef62a4SYucong Sun {
103e1ef62a4SYucong Sun 	const char * const *ns = namespaces;
104e1ef62a4SYucong Sun 	char cmd[128];
105e1ef62a4SYucong Sun 
106e1ef62a4SYucong Sun 	while (*ns) {
107e1ef62a4SYucong Sun 		snprintf(cmd, sizeof(cmd), "ip netns %s %s > /dev/null 2>&1", verb, *ns);
108e1ef62a4SYucong Sun 		system(cmd);
109e1ef62a4SYucong Sun 		ns++;
110e1ef62a4SYucong Sun 	}
111e1ef62a4SYucong Sun }
112e1ef62a4SYucong Sun 
11372f1ba02SDaniel Borkmann enum dev_mode {
11472f1ba02SDaniel Borkmann 	MODE_VETH,
11572f1ba02SDaniel Borkmann };
11672f1ba02SDaniel Borkmann 
117096eccdeSJussi Maki struct netns_setup_result {
11872f1ba02SDaniel Borkmann 	enum dev_mode dev_mode;
11972f1ba02SDaniel Borkmann 	int ifindex_src;
12072f1ba02SDaniel Borkmann 	int ifindex_src_fwd;
12172f1ba02SDaniel Borkmann 	int ifindex_dst;
12272f1ba02SDaniel Borkmann 	int ifindex_dst_fwd;
123096eccdeSJussi Maki };
124096eccdeSJussi Maki 
get_ifaddr(const char * name,char * ifaddr)125096eccdeSJussi Maki static int get_ifaddr(const char *name, char *ifaddr)
126096eccdeSJussi Maki {
127096eccdeSJussi Maki 	char path[PATH_MAX];
128096eccdeSJussi Maki 	FILE *f;
129096eccdeSJussi Maki 	int ret;
130096eccdeSJussi Maki 
131096eccdeSJussi Maki 	snprintf(path, PATH_MAX, "/sys/class/net/%s/address", name);
132096eccdeSJussi Maki 	f = fopen(path, "r");
133096eccdeSJussi Maki 	if (!ASSERT_OK_PTR(f, path))
134096eccdeSJussi Maki 		return -1;
135096eccdeSJussi Maki 
136096eccdeSJussi Maki 	ret = fread(ifaddr, 1, IFADDR_STR_LEN, f);
137096eccdeSJussi Maki 	if (!ASSERT_EQ(ret, IFADDR_STR_LEN, "fread ifaddr")) {
138096eccdeSJussi Maki 		fclose(f);
139096eccdeSJussi Maki 		return -1;
140096eccdeSJussi Maki 	}
141096eccdeSJussi Maki 	fclose(f);
142096eccdeSJussi Maki 	return 0;
143096eccdeSJussi Maki }
144096eccdeSJussi Maki 
netns_setup_links_and_routes(struct netns_setup_result * result)145096eccdeSJussi Maki static int netns_setup_links_and_routes(struct netns_setup_result *result)
146096eccdeSJussi Maki {
1476fd5fb63SJussi Maki 	struct nstoken *nstoken = NULL;
14872f1ba02SDaniel Borkmann 	char src_fwd_addr[IFADDR_STR_LEN+1] = {};
149f02bcb41SMartin KaFai Lau 	char src_addr[IFADDR_STR_LEN + 1] = {};
150096eccdeSJussi Maki 
15172f1ba02SDaniel Borkmann 	if (result->dev_mode == MODE_VETH) {
15272f1ba02SDaniel Borkmann 		SYS(fail, "ip link add src type veth peer name src_fwd");
15372f1ba02SDaniel Borkmann 		SYS(fail, "ip link add dst type veth peer name dst_fwd");
1546fd5fb63SJussi Maki 
15572f1ba02SDaniel Borkmann 		SYS(fail, "ip link set dst_fwd address " MAC_DST_FWD);
15672f1ba02SDaniel Borkmann 		SYS(fail, "ip link set dst address " MAC_DST);
15772f1ba02SDaniel Borkmann 	}
1586fd5fb63SJussi Maki 
15972f1ba02SDaniel Borkmann 	if (get_ifaddr("src_fwd", src_fwd_addr))
160096eccdeSJussi Maki 		goto fail;
161096eccdeSJussi Maki 
162f02bcb41SMartin KaFai Lau 	if (get_ifaddr("src", src_addr))
163f02bcb41SMartin KaFai Lau 		goto fail;
164f02bcb41SMartin KaFai Lau 
16572f1ba02SDaniel Borkmann 	result->ifindex_src = if_nametoindex("src");
16672f1ba02SDaniel Borkmann 	if (!ASSERT_GT(result->ifindex_src, 0, "ifindex_src"))
167096eccdeSJussi Maki 		goto fail;
168052c82dcSMartin KaFai Lau 
16972f1ba02SDaniel Borkmann 	result->ifindex_src_fwd = if_nametoindex("src_fwd");
17072f1ba02SDaniel Borkmann 	if (!ASSERT_GT(result->ifindex_src_fwd, 0, "ifindex_src_fwd"))
171052c82dcSMartin KaFai Lau 		goto fail;
172052c82dcSMartin KaFai Lau 
17372f1ba02SDaniel Borkmann 	result->ifindex_dst = if_nametoindex("dst");
17472f1ba02SDaniel Borkmann 	if (!ASSERT_GT(result->ifindex_dst, 0, "ifindex_dst"))
175052c82dcSMartin KaFai Lau 		goto fail;
176052c82dcSMartin KaFai Lau 
17772f1ba02SDaniel Borkmann 	result->ifindex_dst_fwd = if_nametoindex("dst_fwd");
17872f1ba02SDaniel Borkmann 	if (!ASSERT_GT(result->ifindex_dst_fwd, 0, "ifindex_dst_fwd"))
179096eccdeSJussi Maki 		goto fail;
180096eccdeSJussi Maki 
18172f1ba02SDaniel Borkmann 	SYS(fail, "ip link set src netns " NS_SRC);
18272f1ba02SDaniel Borkmann 	SYS(fail, "ip link set src_fwd netns " NS_FWD);
18372f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dst_fwd netns " NS_FWD);
18472f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dst netns " NS_DST);
185096eccdeSJussi Maki 
186096eccdeSJussi Maki 	/** setup in 'src' namespace */
1876fd5fb63SJussi Maki 	nstoken = open_netns(NS_SRC);
1886fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns src"))
189096eccdeSJussi Maki 		goto fail;
190096eccdeSJussi Maki 
19172f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP4_SRC "/32 dev src");
19272f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP6_SRC "/128 dev src nodad");
19372f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dev src up");
194096eccdeSJussi Maki 
19572f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_DST "/32 dev src scope global");
19672f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_NET "/16 dev src scope global");
19772f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP6_DST "/128 dev src scope global");
198096eccdeSJussi Maki 
19972f1ba02SDaniel Borkmann 	if (result->dev_mode == MODE_VETH) {
20072f1ba02SDaniel Borkmann 		SYS(fail, "ip neigh add " IP4_DST " dev src lladdr %s",
20172f1ba02SDaniel Borkmann 		    src_fwd_addr);
20272f1ba02SDaniel Borkmann 		SYS(fail, "ip neigh add " IP6_DST " dev src lladdr %s",
20372f1ba02SDaniel Borkmann 		    src_fwd_addr);
20472f1ba02SDaniel Borkmann 	}
205096eccdeSJussi Maki 
2066fd5fb63SJussi Maki 	close_netns(nstoken);
2076fd5fb63SJussi Maki 
208096eccdeSJussi Maki 	/** setup in 'fwd' namespace */
2096fd5fb63SJussi Maki 	nstoken = open_netns(NS_FWD);
2106fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
211096eccdeSJussi Maki 		goto fail;
212096eccdeSJussi Maki 
213096eccdeSJussi Maki 	/* The fwd netns automatically gets a v6 LL address / routes, but also
214096eccdeSJussi Maki 	 * needs v4 one in order to start ARP probing. IP4_NET route is added
215096eccdeSJussi Maki 	 * to the endpoints so that the ARP processing will reply.
216096eccdeSJussi Maki 	 */
21772f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP4_SLL "/32 dev src_fwd");
21872f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP4_DLL "/32 dev dst_fwd");
21972f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dev src_fwd up");
22072f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dev dst_fwd up");
221096eccdeSJussi Maki 
22272f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_SRC "/32 dev src_fwd scope global");
22372f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP6_SRC "/128 dev src_fwd scope global");
22472f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_DST "/32 dev dst_fwd scope global");
22572f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP6_DST "/128 dev dst_fwd scope global");
226096eccdeSJussi Maki 
227f02bcb41SMartin KaFai Lau 	if (result->dev_mode == MODE_VETH) {
228f02bcb41SMartin KaFai Lau 		SYS(fail, "ip neigh add " IP4_SRC " dev src_fwd lladdr %s", src_addr);
229f02bcb41SMartin KaFai Lau 		SYS(fail, "ip neigh add " IP6_SRC " dev src_fwd lladdr %s", src_addr);
230f02bcb41SMartin KaFai Lau 		SYS(fail, "ip neigh add " IP4_DST " dev dst_fwd lladdr %s", MAC_DST);
231f02bcb41SMartin KaFai Lau 		SYS(fail, "ip neigh add " IP6_DST " dev dst_fwd lladdr %s", MAC_DST);
232f02bcb41SMartin KaFai Lau 	}
233f02bcb41SMartin KaFai Lau 
2346fd5fb63SJussi Maki 	close_netns(nstoken);
2356fd5fb63SJussi Maki 
236096eccdeSJussi Maki 	/** setup in 'dst' namespace */
2376fd5fb63SJussi Maki 	nstoken = open_netns(NS_DST);
2386fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns dst"))
239096eccdeSJussi Maki 		goto fail;
240096eccdeSJussi Maki 
24172f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP4_DST "/32 dev dst");
24272f1ba02SDaniel Borkmann 	SYS(fail, "ip addr add " IP6_DST "/128 dev dst nodad");
24372f1ba02SDaniel Borkmann 	SYS(fail, "ip link set dev dst up");
244*a9800dc6SMartin KaFai Lau 	SYS(fail, "ip link set dev lo up");
245096eccdeSJussi Maki 
24672f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_SRC "/32 dev dst scope global");
24772f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP4_NET "/16 dev dst scope global");
24872f1ba02SDaniel Borkmann 	SYS(fail, "ip route add " IP6_SRC "/128 dev dst scope global");
249096eccdeSJussi Maki 
25072f1ba02SDaniel Borkmann 	if (result->dev_mode == MODE_VETH) {
25172f1ba02SDaniel Borkmann 		SYS(fail, "ip neigh add " IP4_SRC " dev dst lladdr " MAC_DST_FWD);
25272f1ba02SDaniel Borkmann 		SYS(fail, "ip neigh add " IP6_SRC " dev dst lladdr " MAC_DST_FWD);
25372f1ba02SDaniel Borkmann 	}
254096eccdeSJussi Maki 
2556fd5fb63SJussi Maki 	close_netns(nstoken);
2566fd5fb63SJussi Maki 
257096eccdeSJussi Maki 	return 0;
258096eccdeSJussi Maki fail:
2596fd5fb63SJussi Maki 	if (nstoken)
2606fd5fb63SJussi Maki 		close_netns(nstoken);
261096eccdeSJussi Maki 	return -1;
262096eccdeSJussi Maki }
263096eccdeSJussi Maki 
qdisc_clsact_create(struct bpf_tc_hook * qdisc_hook,int ifindex)26457d0863fSMartin KaFai Lau static int qdisc_clsact_create(struct bpf_tc_hook *qdisc_hook, int ifindex)
26557d0863fSMartin KaFai Lau {
26657d0863fSMartin KaFai Lau 	char err_str[128], ifname[16];
26757d0863fSMartin KaFai Lau 	int err;
26857d0863fSMartin KaFai Lau 
26957d0863fSMartin KaFai Lau 	qdisc_hook->ifindex = ifindex;
27057d0863fSMartin KaFai Lau 	qdisc_hook->attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS;
27157d0863fSMartin KaFai Lau 	err = bpf_tc_hook_create(qdisc_hook);
27257d0863fSMartin KaFai Lau 	snprintf(err_str, sizeof(err_str),
27357d0863fSMartin KaFai Lau 		 "qdisc add dev %s clsact",
27457d0863fSMartin KaFai Lau 		 if_indextoname(qdisc_hook->ifindex, ifname) ? : "<unknown_iface>");
27557d0863fSMartin KaFai Lau 	err_str[sizeof(err_str) - 1] = 0;
27657d0863fSMartin KaFai Lau 	ASSERT_OK(err, err_str);
27757d0863fSMartin KaFai Lau 
27857d0863fSMartin KaFai Lau 	return err;
27957d0863fSMartin KaFai Lau }
28057d0863fSMartin KaFai Lau 
xgress_filter_add(struct bpf_tc_hook * qdisc_hook,enum bpf_tc_attach_point xgress,const struct bpf_program * prog,int priority)28157d0863fSMartin KaFai Lau static int xgress_filter_add(struct bpf_tc_hook *qdisc_hook,
28257d0863fSMartin KaFai Lau 			     enum bpf_tc_attach_point xgress,
28357d0863fSMartin KaFai Lau 			     const struct bpf_program *prog, int priority)
28457d0863fSMartin KaFai Lau {
28557d0863fSMartin KaFai Lau 	LIBBPF_OPTS(bpf_tc_opts, tc_attach);
28657d0863fSMartin KaFai Lau 	char err_str[128], ifname[16];
28757d0863fSMartin KaFai Lau 	int err;
28857d0863fSMartin KaFai Lau 
28957d0863fSMartin KaFai Lau 	qdisc_hook->attach_point = xgress;
29057d0863fSMartin KaFai Lau 	tc_attach.prog_fd = bpf_program__fd(prog);
29157d0863fSMartin KaFai Lau 	tc_attach.priority = priority;
29257d0863fSMartin KaFai Lau 	err = bpf_tc_attach(qdisc_hook, &tc_attach);
29357d0863fSMartin KaFai Lau 	snprintf(err_str, sizeof(err_str),
29457d0863fSMartin KaFai Lau 		 "filter add dev %s %s prio %d bpf da %s",
29557d0863fSMartin KaFai Lau 		 if_indextoname(qdisc_hook->ifindex, ifname) ? : "<unknown_iface>",
29657d0863fSMartin KaFai Lau 		 xgress == BPF_TC_INGRESS ? "ingress" : "egress",
29757d0863fSMartin KaFai Lau 		 priority, bpf_program__name(prog));
29857d0863fSMartin KaFai Lau 	err_str[sizeof(err_str) - 1] = 0;
29957d0863fSMartin KaFai Lau 	ASSERT_OK(err, err_str);
30057d0863fSMartin KaFai Lau 
30157d0863fSMartin KaFai Lau 	return err;
30257d0863fSMartin KaFai Lau }
30357d0863fSMartin KaFai Lau 
30457d0863fSMartin KaFai Lau #define QDISC_CLSACT_CREATE(qdisc_hook, ifindex) ({		\
30557d0863fSMartin KaFai Lau 	if ((err = qdisc_clsact_create(qdisc_hook, ifindex)))	\
30657d0863fSMartin KaFai Lau 		goto fail;					\
30757d0863fSMartin KaFai Lau })
30857d0863fSMartin KaFai Lau 
30957d0863fSMartin KaFai Lau #define XGRESS_FILTER_ADD(qdisc_hook, xgress, prog, priority) ({		\
31057d0863fSMartin KaFai Lau 	if ((err = xgress_filter_add(qdisc_hook, xgress, prog, priority)))	\
31157d0863fSMartin KaFai Lau 		goto fail;							\
31257d0863fSMartin KaFai Lau })
31357d0863fSMartin KaFai Lau 
netns_load_bpf(const struct bpf_program * src_prog,const struct bpf_program * dst_prog,const struct bpf_program * chk_prog,const struct netns_setup_result * setup_result)3145dc42a7fSMartin KaFai Lau static int netns_load_bpf(const struct bpf_program *src_prog,
3155dc42a7fSMartin KaFai Lau 			  const struct bpf_program *dst_prog,
3165dc42a7fSMartin KaFai Lau 			  const struct bpf_program *chk_prog,
3175dc42a7fSMartin KaFai Lau 			  const struct netns_setup_result *setup_result)
318096eccdeSJussi Maki {
31972f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_src_fwd);
32072f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_dst_fwd);
3215dc42a7fSMartin KaFai Lau 	int err;
322096eccdeSJussi Maki 
32372f1ba02SDaniel Borkmann 	/* tc qdisc add dev src_fwd clsact */
32472f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_src_fwd, setup_result->ifindex_src_fwd);
32572f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd ingress bpf da src_prog */
32672f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_INGRESS, src_prog, 0);
32772f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd egress bpf da chk_prog */
32872f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_EGRESS, chk_prog, 0);
3295dc42a7fSMartin KaFai Lau 
33072f1ba02SDaniel Borkmann 	/* tc qdisc add dev dst_fwd clsact */
33172f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_dst_fwd, setup_result->ifindex_dst_fwd);
33272f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd ingress bpf da dst_prog */
33372f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_INGRESS, dst_prog, 0);
33472f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd egress bpf da chk_prog */
33572f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_EGRESS, chk_prog, 0);
336096eccdeSJussi Maki 
337096eccdeSJussi Maki 	return 0;
338096eccdeSJussi Maki fail:
339096eccdeSJussi Maki 	return -1;
340096eccdeSJussi Maki }
341096eccdeSJussi Maki 
test_tcp(int family,const char * addr,__u16 port)342096eccdeSJussi Maki static void test_tcp(int family, const char *addr, __u16 port)
343096eccdeSJussi Maki {
344096eccdeSJussi Maki 	int listen_fd = -1, accept_fd = -1, client_fd = -1;
345096eccdeSJussi Maki 	char buf[] = "testing testing";
346096eccdeSJussi Maki 	int n;
3476fd5fb63SJussi Maki 	struct nstoken *nstoken;
348096eccdeSJussi Maki 
3496fd5fb63SJussi Maki 	nstoken = open_netns(NS_DST);
3506fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns dst"))
351096eccdeSJussi Maki 		return;
352096eccdeSJussi Maki 
353096eccdeSJussi Maki 	listen_fd = start_server(family, SOCK_STREAM, addr, port, 0);
354096eccdeSJussi Maki 	if (!ASSERT_GE(listen_fd, 0, "listen"))
355096eccdeSJussi Maki 		goto done;
356096eccdeSJussi Maki 
3576fd5fb63SJussi Maki 	close_netns(nstoken);
3586fd5fb63SJussi Maki 	nstoken = open_netns(NS_SRC);
3596fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns src"))
360096eccdeSJussi Maki 		goto done;
361096eccdeSJussi Maki 
362096eccdeSJussi Maki 	client_fd = connect_to_fd(listen_fd, TIMEOUT_MILLIS);
363096eccdeSJussi Maki 	if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
364096eccdeSJussi Maki 		goto done;
365096eccdeSJussi Maki 
366096eccdeSJussi Maki 	accept_fd = accept(listen_fd, NULL, NULL);
367096eccdeSJussi Maki 	if (!ASSERT_GE(accept_fd, 0, "accept"))
368096eccdeSJussi Maki 		goto done;
369096eccdeSJussi Maki 
370096eccdeSJussi Maki 	if (!ASSERT_OK(settimeo(accept_fd, TIMEOUT_MILLIS), "settimeo"))
371096eccdeSJussi Maki 		goto done;
372096eccdeSJussi Maki 
373096eccdeSJussi Maki 	n = write(client_fd, buf, sizeof(buf));
374096eccdeSJussi Maki 	if (!ASSERT_EQ(n, sizeof(buf), "send to server"))
375096eccdeSJussi Maki 		goto done;
376096eccdeSJussi Maki 
377096eccdeSJussi Maki 	n = read(accept_fd, buf, sizeof(buf));
378096eccdeSJussi Maki 	ASSERT_EQ(n, sizeof(buf), "recv from server");
379096eccdeSJussi Maki 
380096eccdeSJussi Maki done:
3816fd5fb63SJussi Maki 	if (nstoken)
3826fd5fb63SJussi Maki 		close_netns(nstoken);
383096eccdeSJussi Maki 	if (listen_fd >= 0)
384096eccdeSJussi Maki 		close(listen_fd);
385096eccdeSJussi Maki 	if (accept_fd >= 0)
386096eccdeSJussi Maki 		close(accept_fd);
387096eccdeSJussi Maki 	if (client_fd >= 0)
388096eccdeSJussi Maki 		close(client_fd);
389096eccdeSJussi Maki }
390096eccdeSJussi Maki 
test_ping(int family,const char * addr)391096eccdeSJussi Maki static int test_ping(int family, const char *addr)
392096eccdeSJussi Maki {
393b61987d3SHangbin Liu 	SYS(fail, "ip netns exec " NS_SRC " %s " PING_ARGS " %s > /dev/null", ping_command(family), addr);
394096eccdeSJussi Maki 	return 0;
395096eccdeSJussi Maki fail:
396096eccdeSJussi Maki 	return -1;
397096eccdeSJussi Maki }
398096eccdeSJussi Maki 
test_connectivity(void)399096eccdeSJussi Maki static void test_connectivity(void)
400096eccdeSJussi Maki {
401096eccdeSJussi Maki 	test_tcp(AF_INET, IP4_DST, IP4_PORT);
402096eccdeSJussi Maki 	test_ping(AF_INET, IP4_DST);
403096eccdeSJussi Maki 	test_tcp(AF_INET6, IP6_DST, IP6_PORT);
404096eccdeSJussi Maki 	test_ping(AF_INET6, IP6_DST);
405096eccdeSJussi Maki }
406096eccdeSJussi Maki 
set_forwarding(bool enable)4076fd5fb63SJussi Maki static int set_forwarding(bool enable)
4086fd5fb63SJussi Maki {
4096fd5fb63SJussi Maki 	int err;
4106fd5fb63SJussi Maki 
4116fd5fb63SJussi Maki 	err = write_file("/proc/sys/net/ipv4/ip_forward", enable ? "1" : "0");
4126fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "set ipv4.ip_forward=0"))
4136fd5fb63SJussi Maki 		return err;
4146fd5fb63SJussi Maki 
4156fd5fb63SJussi Maki 	err = write_file("/proc/sys/net/ipv6/conf/all/forwarding", enable ? "1" : "0");
4166fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "set ipv6.forwarding=0"))
4176fd5fb63SJussi Maki 		return err;
4186fd5fb63SJussi Maki 
4196fd5fb63SJussi Maki 	return 0;
4206fd5fb63SJussi Maki }
4216fd5fb63SJussi Maki 
__rcv_tstamp(int fd,const char * expected,size_t s,__u64 * tstamp)422*a9800dc6SMartin KaFai Lau static int __rcv_tstamp(int fd, const char *expected, size_t s, __u64 *tstamp)
423c803475fSMartin KaFai Lau {
424c803475fSMartin KaFai Lau 	struct __kernel_timespec pkt_ts = {};
425c803475fSMartin KaFai Lau 	char ctl[CMSG_SPACE(sizeof(pkt_ts))];
426c803475fSMartin KaFai Lau 	struct timespec now_ts;
427c803475fSMartin KaFai Lau 	struct msghdr msg = {};
428c803475fSMartin KaFai Lau 	__u64 now_ns, pkt_ns;
429c803475fSMartin KaFai Lau 	struct cmsghdr *cmsg;
430c803475fSMartin KaFai Lau 	struct iovec iov;
431c803475fSMartin KaFai Lau 	char data[32];
432c803475fSMartin KaFai Lau 	int ret;
433c803475fSMartin KaFai Lau 
434c803475fSMartin KaFai Lau 	iov.iov_base = data;
435c803475fSMartin KaFai Lau 	iov.iov_len = sizeof(data);
436c803475fSMartin KaFai Lau 	msg.msg_iov = &iov;
437c803475fSMartin KaFai Lau 	msg.msg_iovlen = 1;
438c803475fSMartin KaFai Lau 	msg.msg_control = &ctl;
439c803475fSMartin KaFai Lau 	msg.msg_controllen = sizeof(ctl);
440c803475fSMartin KaFai Lau 
441c803475fSMartin KaFai Lau 	ret = recvmsg(fd, &msg, 0);
442c803475fSMartin KaFai Lau 	if (!ASSERT_EQ(ret, s, "recvmsg"))
443*a9800dc6SMartin KaFai Lau 		return -1;
444c803475fSMartin KaFai Lau 	ASSERT_STRNEQ(data, expected, s, "expected rcv data");
445c803475fSMartin KaFai Lau 
446c803475fSMartin KaFai Lau 	cmsg = CMSG_FIRSTHDR(&msg);
447c803475fSMartin KaFai Lau 	if (cmsg && cmsg->cmsg_level == SOL_SOCKET &&
448c803475fSMartin KaFai Lau 	    cmsg->cmsg_type == SO_TIMESTAMPNS_NEW)
449c803475fSMartin KaFai Lau 		memcpy(&pkt_ts, CMSG_DATA(cmsg), sizeof(pkt_ts));
450c803475fSMartin KaFai Lau 
451c803475fSMartin KaFai Lau 	pkt_ns = pkt_ts.tv_sec * NSEC_PER_SEC + pkt_ts.tv_nsec;
452*a9800dc6SMartin KaFai Lau 	if (tstamp) {
453*a9800dc6SMartin KaFai Lau 		/* caller will check the tstamp itself */
454*a9800dc6SMartin KaFai Lau 		*tstamp = pkt_ns;
455*a9800dc6SMartin KaFai Lau 		return 0;
456*a9800dc6SMartin KaFai Lau 	}
457*a9800dc6SMartin KaFai Lau 
458c803475fSMartin KaFai Lau 	ASSERT_NEQ(pkt_ns, 0, "pkt rcv tstamp");
459c803475fSMartin KaFai Lau 
460c803475fSMartin KaFai Lau 	ret = clock_gettime(CLOCK_REALTIME, &now_ts);
461c803475fSMartin KaFai Lau 	ASSERT_OK(ret, "clock_gettime");
462c803475fSMartin KaFai Lau 	now_ns = now_ts.tv_sec * NSEC_PER_SEC + now_ts.tv_nsec;
463c803475fSMartin KaFai Lau 
464c803475fSMartin KaFai Lau 	if (ASSERT_GE(now_ns, pkt_ns, "check rcv tstamp"))
465c803475fSMartin KaFai Lau 		ASSERT_LT(now_ns - pkt_ns, 5 * NSEC_PER_SEC,
466c803475fSMartin KaFai Lau 			  "check rcv tstamp");
467*a9800dc6SMartin KaFai Lau 	return 0;
468*a9800dc6SMartin KaFai Lau }
469*a9800dc6SMartin KaFai Lau 
rcv_tstamp(int fd,const char * expected,size_t s)470*a9800dc6SMartin KaFai Lau static void rcv_tstamp(int fd, const char *expected, size_t s)
471*a9800dc6SMartin KaFai Lau {
472*a9800dc6SMartin KaFai Lau 	__rcv_tstamp(fd, expected, s, NULL);
473*a9800dc6SMartin KaFai Lau }
474*a9800dc6SMartin KaFai Lau 
wait_netstamp_needed_key(void)475*a9800dc6SMartin KaFai Lau static int wait_netstamp_needed_key(void)
476*a9800dc6SMartin KaFai Lau {
477*a9800dc6SMartin KaFai Lau 	int opt = 1, srv_fd = -1, cli_fd = -1, nretries = 0, err, n;
478*a9800dc6SMartin KaFai Lau 	char buf[] = "testing testing";
479*a9800dc6SMartin KaFai Lau 	struct nstoken *nstoken;
480*a9800dc6SMartin KaFai Lau 	__u64 tstamp = 0;
481*a9800dc6SMartin KaFai Lau 
482*a9800dc6SMartin KaFai Lau 	nstoken = open_netns(NS_DST);
483*a9800dc6SMartin KaFai Lau 	if (!nstoken)
484*a9800dc6SMartin KaFai Lau 		return -1;
485*a9800dc6SMartin KaFai Lau 
486*a9800dc6SMartin KaFai Lau 	srv_fd = start_server(AF_INET6, SOCK_DGRAM, "::1", 0, 0);
487*a9800dc6SMartin KaFai Lau 	if (!ASSERT_GE(srv_fd, 0, "start_server"))
488*a9800dc6SMartin KaFai Lau 		goto done;
489*a9800dc6SMartin KaFai Lau 
490*a9800dc6SMartin KaFai Lau 	err = setsockopt(srv_fd, SOL_SOCKET, SO_TIMESTAMPNS_NEW,
491*a9800dc6SMartin KaFai Lau 			 &opt, sizeof(opt));
492*a9800dc6SMartin KaFai Lau 	if (!ASSERT_OK(err, "setsockopt(SO_TIMESTAMPNS_NEW)"))
493*a9800dc6SMartin KaFai Lau 		goto done;
494*a9800dc6SMartin KaFai Lau 
495*a9800dc6SMartin KaFai Lau 	cli_fd = connect_to_fd(srv_fd, TIMEOUT_MILLIS);
496*a9800dc6SMartin KaFai Lau 	if (!ASSERT_GE(cli_fd, 0, "connect_to_fd"))
497*a9800dc6SMartin KaFai Lau 		goto done;
498*a9800dc6SMartin KaFai Lau 
499*a9800dc6SMartin KaFai Lau again:
500*a9800dc6SMartin KaFai Lau 	n = write(cli_fd, buf, sizeof(buf));
501*a9800dc6SMartin KaFai Lau 	if (!ASSERT_EQ(n, sizeof(buf), "send to server"))
502*a9800dc6SMartin KaFai Lau 		goto done;
503*a9800dc6SMartin KaFai Lau 	err = __rcv_tstamp(srv_fd, buf, sizeof(buf), &tstamp);
504*a9800dc6SMartin KaFai Lau 	if (!ASSERT_OK(err, "__rcv_tstamp"))
505*a9800dc6SMartin KaFai Lau 		goto done;
506*a9800dc6SMartin KaFai Lau 	if (!tstamp && nretries++ < 5) {
507*a9800dc6SMartin KaFai Lau 		sleep(1);
508*a9800dc6SMartin KaFai Lau 		printf("netstamp_needed_key retry#%d\n", nretries);
509*a9800dc6SMartin KaFai Lau 		goto again;
510*a9800dc6SMartin KaFai Lau 	}
511*a9800dc6SMartin KaFai Lau 
512*a9800dc6SMartin KaFai Lau done:
513*a9800dc6SMartin KaFai Lau 	if (!tstamp && srv_fd != -1) {
514*a9800dc6SMartin KaFai Lau 		close(srv_fd);
515*a9800dc6SMartin KaFai Lau 		srv_fd = -1;
516*a9800dc6SMartin KaFai Lau 	}
517*a9800dc6SMartin KaFai Lau 	if (cli_fd != -1)
518*a9800dc6SMartin KaFai Lau 		close(cli_fd);
519*a9800dc6SMartin KaFai Lau 	close_netns(nstoken);
520*a9800dc6SMartin KaFai Lau 	return srv_fd;
521c803475fSMartin KaFai Lau }
522c803475fSMartin KaFai Lau 
snd_tstamp(int fd,char * b,size_t s)523c803475fSMartin KaFai Lau static void snd_tstamp(int fd, char *b, size_t s)
524c803475fSMartin KaFai Lau {
525c803475fSMartin KaFai Lau 	struct sock_txtime opt = { .clockid = CLOCK_TAI };
526c803475fSMartin KaFai Lau 	char ctl[CMSG_SPACE(sizeof(__u64))];
527c803475fSMartin KaFai Lau 	struct timespec now_ts;
528c803475fSMartin KaFai Lau 	struct msghdr msg = {};
529c803475fSMartin KaFai Lau 	struct cmsghdr *cmsg;
530c803475fSMartin KaFai Lau 	struct iovec iov;
531c803475fSMartin KaFai Lau 	__u64 now_ns;
532c803475fSMartin KaFai Lau 	int ret;
533c803475fSMartin KaFai Lau 
534c803475fSMartin KaFai Lau 	ret = clock_gettime(CLOCK_TAI, &now_ts);
535c803475fSMartin KaFai Lau 	ASSERT_OK(ret, "clock_get_time(CLOCK_TAI)");
536c803475fSMartin KaFai Lau 	now_ns = now_ts.tv_sec * NSEC_PER_SEC + now_ts.tv_nsec;
537c803475fSMartin KaFai Lau 
538c803475fSMartin KaFai Lau 	iov.iov_base = b;
539c803475fSMartin KaFai Lau 	iov.iov_len = s;
540c803475fSMartin KaFai Lau 	msg.msg_iov = &iov;
541c803475fSMartin KaFai Lau 	msg.msg_iovlen = 1;
542c803475fSMartin KaFai Lau 	msg.msg_control = &ctl;
543c803475fSMartin KaFai Lau 	msg.msg_controllen = sizeof(ctl);
544c803475fSMartin KaFai Lau 
545c803475fSMartin KaFai Lau 	cmsg = CMSG_FIRSTHDR(&msg);
546c803475fSMartin KaFai Lau 	cmsg->cmsg_level = SOL_SOCKET;
547c803475fSMartin KaFai Lau 	cmsg->cmsg_type = SCM_TXTIME;
548c803475fSMartin KaFai Lau 	cmsg->cmsg_len = CMSG_LEN(sizeof(now_ns));
549c803475fSMartin KaFai Lau 	*(__u64 *)CMSG_DATA(cmsg) = now_ns;
550c803475fSMartin KaFai Lau 
551c803475fSMartin KaFai Lau 	ret = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &opt, sizeof(opt));
552c803475fSMartin KaFai Lau 	ASSERT_OK(ret, "setsockopt(SO_TXTIME)");
553c803475fSMartin KaFai Lau 
554c803475fSMartin KaFai Lau 	ret = sendmsg(fd, &msg, 0);
555c803475fSMartin KaFai Lau 	ASSERT_EQ(ret, s, "sendmsg");
556c803475fSMartin KaFai Lau }
557c803475fSMartin KaFai Lau 
test_inet_dtime(int family,int type,const char * addr,__u16 port)558c803475fSMartin KaFai Lau static void test_inet_dtime(int family, int type, const char *addr, __u16 port)
559c803475fSMartin KaFai Lau {
560c803475fSMartin KaFai Lau 	int opt = 1, accept_fd = -1, client_fd = -1, listen_fd, err;
561c803475fSMartin KaFai Lau 	char buf[] = "testing testing";
562c803475fSMartin KaFai Lau 	struct nstoken *nstoken;
563c803475fSMartin KaFai Lau 
564c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_DST);
565c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns dst"))
566c803475fSMartin KaFai Lau 		return;
567c803475fSMartin KaFai Lau 	listen_fd = start_server(family, type, addr, port, 0);
568c803475fSMartin KaFai Lau 	close_netns(nstoken);
569c803475fSMartin KaFai Lau 
570c803475fSMartin KaFai Lau 	if (!ASSERT_GE(listen_fd, 0, "listen"))
571c803475fSMartin KaFai Lau 		return;
572c803475fSMartin KaFai Lau 
573c803475fSMartin KaFai Lau 	/* Ensure the kernel puts the (rcv) timestamp for all skb */
574c803475fSMartin KaFai Lau 	err = setsockopt(listen_fd, SOL_SOCKET, SO_TIMESTAMPNS_NEW,
575c803475fSMartin KaFai Lau 			 &opt, sizeof(opt));
576c803475fSMartin KaFai Lau 	if (!ASSERT_OK(err, "setsockopt(SO_TIMESTAMPNS_NEW)"))
577c803475fSMartin KaFai Lau 		goto done;
578c803475fSMartin KaFai Lau 
579c803475fSMartin KaFai Lau 	if (type == SOCK_STREAM) {
580c803475fSMartin KaFai Lau 		/* Ensure the kernel set EDT when sending out rst/ack
581c803475fSMartin KaFai Lau 		 * from the kernel's ctl_sk.
582c803475fSMartin KaFai Lau 		 */
583c803475fSMartin KaFai Lau 		err = setsockopt(listen_fd, SOL_TCP, TCP_TX_DELAY, &opt,
584c803475fSMartin KaFai Lau 				 sizeof(opt));
585c803475fSMartin KaFai Lau 		if (!ASSERT_OK(err, "setsockopt(TCP_TX_DELAY)"))
586c803475fSMartin KaFai Lau 			goto done;
587c803475fSMartin KaFai Lau 	}
588c803475fSMartin KaFai Lau 
589c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_SRC);
590c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns src"))
591c803475fSMartin KaFai Lau 		goto done;
592c803475fSMartin KaFai Lau 	client_fd = connect_to_fd(listen_fd, TIMEOUT_MILLIS);
593c803475fSMartin KaFai Lau 	close_netns(nstoken);
594c803475fSMartin KaFai Lau 
595c803475fSMartin KaFai Lau 	if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
596c803475fSMartin KaFai Lau 		goto done;
597c803475fSMartin KaFai Lau 
598c803475fSMartin KaFai Lau 	if (type == SOCK_STREAM) {
599c803475fSMartin KaFai Lau 		int n;
600c803475fSMartin KaFai Lau 
601c803475fSMartin KaFai Lau 		accept_fd = accept(listen_fd, NULL, NULL);
602c803475fSMartin KaFai Lau 		if (!ASSERT_GE(accept_fd, 0, "accept"))
603c803475fSMartin KaFai Lau 			goto done;
604c803475fSMartin KaFai Lau 
605c803475fSMartin KaFai Lau 		n = write(client_fd, buf, sizeof(buf));
606c803475fSMartin KaFai Lau 		if (!ASSERT_EQ(n, sizeof(buf), "send to server"))
607c803475fSMartin KaFai Lau 			goto done;
608c803475fSMartin KaFai Lau 		rcv_tstamp(accept_fd, buf, sizeof(buf));
609c803475fSMartin KaFai Lau 	} else {
610c803475fSMartin KaFai Lau 		snd_tstamp(client_fd, buf, sizeof(buf));
611c803475fSMartin KaFai Lau 		rcv_tstamp(listen_fd, buf, sizeof(buf));
612c803475fSMartin KaFai Lau 	}
613c803475fSMartin KaFai Lau 
614c803475fSMartin KaFai Lau done:
615c803475fSMartin KaFai Lau 	close(listen_fd);
616c803475fSMartin KaFai Lau 	if (accept_fd != -1)
617c803475fSMartin KaFai Lau 		close(accept_fd);
618c803475fSMartin KaFai Lau 	if (client_fd != -1)
619c803475fSMartin KaFai Lau 		close(client_fd);
620c803475fSMartin KaFai Lau }
621c803475fSMartin KaFai Lau 
netns_load_dtime_bpf(struct test_tc_dtime * skel,const struct netns_setup_result * setup_result)62257d0863fSMartin KaFai Lau static int netns_load_dtime_bpf(struct test_tc_dtime *skel,
62357d0863fSMartin KaFai Lau 				const struct netns_setup_result *setup_result)
624c803475fSMartin KaFai Lau {
62572f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_src_fwd);
62672f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_dst_fwd);
62772f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_src);
62872f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_dst);
629c803475fSMartin KaFai Lau 	struct nstoken *nstoken;
63057d0863fSMartin KaFai Lau 	int err;
631c803475fSMartin KaFai Lau 
632c803475fSMartin KaFai Lau 	/* setup ns_src tc progs */
633c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_SRC);
634c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns " NS_SRC))
635c803475fSMartin KaFai Lau 		return -1;
63672f1ba02SDaniel Borkmann 	/* tc qdisc add dev src clsact */
63772f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_src, setup_result->ifindex_src);
63872f1ba02SDaniel Borkmann 	/* tc filter add dev src ingress bpf da ingress_host */
63972f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src, BPF_TC_INGRESS, skel->progs.ingress_host, 0);
64072f1ba02SDaniel Borkmann 	/* tc filter add dev src egress bpf da egress_host */
64172f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src, BPF_TC_EGRESS, skel->progs.egress_host, 0);
642c803475fSMartin KaFai Lau 	close_netns(nstoken);
643c803475fSMartin KaFai Lau 
644c803475fSMartin KaFai Lau 	/* setup ns_dst tc progs */
645c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_DST);
646c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns " NS_DST))
647c803475fSMartin KaFai Lau 		return -1;
64872f1ba02SDaniel Borkmann 	/* tc qdisc add dev dst clsact */
64972f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_dst, setup_result->ifindex_dst);
65072f1ba02SDaniel Borkmann 	/* tc filter add dev dst ingress bpf da ingress_host */
65172f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst, BPF_TC_INGRESS, skel->progs.ingress_host, 0);
65272f1ba02SDaniel Borkmann 	/* tc filter add dev dst egress bpf da egress_host */
65372f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst, BPF_TC_EGRESS, skel->progs.egress_host, 0);
654c803475fSMartin KaFai Lau 	close_netns(nstoken);
655c803475fSMartin KaFai Lau 
656c803475fSMartin KaFai Lau 	/* setup ns_fwd tc progs */
657c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_FWD);
658c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns " NS_FWD))
659c803475fSMartin KaFai Lau 		return -1;
66072f1ba02SDaniel Borkmann 	/* tc qdisc add dev dst_fwd clsact */
66172f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_dst_fwd, setup_result->ifindex_dst_fwd);
66272f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd ingress prio 100 bpf da ingress_fwdns_prio100 */
66372f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_INGRESS,
66457d0863fSMartin KaFai Lau 			  skel->progs.ingress_fwdns_prio100, 100);
66572f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd ingress prio 101 bpf da ingress_fwdns_prio101 */
66672f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_INGRESS,
66757d0863fSMartin KaFai Lau 			  skel->progs.ingress_fwdns_prio101, 101);
66872f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd egress prio 100 bpf da egress_fwdns_prio100 */
66972f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_EGRESS,
67057d0863fSMartin KaFai Lau 			  skel->progs.egress_fwdns_prio100, 100);
67172f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd egress prio 101 bpf da egress_fwdns_prio101 */
67272f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_EGRESS,
67357d0863fSMartin KaFai Lau 			  skel->progs.egress_fwdns_prio101, 101);
67457d0863fSMartin KaFai Lau 
67572f1ba02SDaniel Borkmann 	/* tc qdisc add dev src_fwd clsact */
67672f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_src_fwd, setup_result->ifindex_src_fwd);
67772f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd ingress prio 100 bpf da ingress_fwdns_prio100 */
67872f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_INGRESS,
67957d0863fSMartin KaFai Lau 			  skel->progs.ingress_fwdns_prio100, 100);
68072f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd ingress prio 101 bpf da ingress_fwdns_prio101 */
68172f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_INGRESS,
68257d0863fSMartin KaFai Lau 			  skel->progs.ingress_fwdns_prio101, 101);
68372f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd egress prio 100 bpf da egress_fwdns_prio100 */
68472f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_EGRESS,
68557d0863fSMartin KaFai Lau 			  skel->progs.egress_fwdns_prio100, 100);
68672f1ba02SDaniel Borkmann 	/* tc filter add dev src_fwd egress prio 101 bpf da egress_fwdns_prio101 */
68772f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_src_fwd, BPF_TC_EGRESS,
68857d0863fSMartin KaFai Lau 			  skel->progs.egress_fwdns_prio101, 101);
689c803475fSMartin KaFai Lau 	close_netns(nstoken);
690c803475fSMartin KaFai Lau 	return 0;
691c803475fSMartin KaFai Lau 
692c803475fSMartin KaFai Lau fail:
693c803475fSMartin KaFai Lau 	close_netns(nstoken);
69457d0863fSMartin KaFai Lau 	return err;
695c803475fSMartin KaFai Lau }
696c803475fSMartin KaFai Lau 
697c803475fSMartin KaFai Lau enum {
698c803475fSMartin KaFai Lau 	INGRESS_FWDNS_P100,
699c803475fSMartin KaFai Lau 	INGRESS_FWDNS_P101,
700c803475fSMartin KaFai Lau 	EGRESS_FWDNS_P100,
701c803475fSMartin KaFai Lau 	EGRESS_FWDNS_P101,
702c803475fSMartin KaFai Lau 	INGRESS_ENDHOST,
703c803475fSMartin KaFai Lau 	EGRESS_ENDHOST,
704c803475fSMartin KaFai Lau 	SET_DTIME,
705c803475fSMartin KaFai Lau 	__MAX_CNT,
706c803475fSMartin KaFai Lau };
707c803475fSMartin KaFai Lau 
708c803475fSMartin KaFai Lau const char *cnt_names[] = {
709c803475fSMartin KaFai Lau 	"ingress_fwdns_p100",
710c803475fSMartin KaFai Lau 	"ingress_fwdns_p101",
711c803475fSMartin KaFai Lau 	"egress_fwdns_p100",
712c803475fSMartin KaFai Lau 	"egress_fwdns_p101",
713c803475fSMartin KaFai Lau 	"ingress_endhost",
714c803475fSMartin KaFai Lau 	"egress_endhost",
715c803475fSMartin KaFai Lau 	"set_dtime",
716c803475fSMartin KaFai Lau };
717c803475fSMartin KaFai Lau 
718c803475fSMartin KaFai Lau enum {
719c803475fSMartin KaFai Lau 	TCP_IP6_CLEAR_DTIME,
720c803475fSMartin KaFai Lau 	TCP_IP4,
721c803475fSMartin KaFai Lau 	TCP_IP6,
722c803475fSMartin KaFai Lau 	UDP_IP4,
723c803475fSMartin KaFai Lau 	UDP_IP6,
724c803475fSMartin KaFai Lau 	TCP_IP4_RT_FWD,
725c803475fSMartin KaFai Lau 	TCP_IP6_RT_FWD,
726c803475fSMartin KaFai Lau 	UDP_IP4_RT_FWD,
727c803475fSMartin KaFai Lau 	UDP_IP6_RT_FWD,
728c803475fSMartin KaFai Lau 	UKN_TEST,
729c803475fSMartin KaFai Lau 	__NR_TESTS,
730c803475fSMartin KaFai Lau };
731c803475fSMartin KaFai Lau 
732c803475fSMartin KaFai Lau const char *test_names[] = {
733c803475fSMartin KaFai Lau 	"tcp ip6 clear dtime",
734c803475fSMartin KaFai Lau 	"tcp ip4",
735c803475fSMartin KaFai Lau 	"tcp ip6",
736c803475fSMartin KaFai Lau 	"udp ip4",
737c803475fSMartin KaFai Lau 	"udp ip6",
738c803475fSMartin KaFai Lau 	"tcp ip4 rt fwd",
739c803475fSMartin KaFai Lau 	"tcp ip6 rt fwd",
740c803475fSMartin KaFai Lau 	"udp ip4 rt fwd",
741c803475fSMartin KaFai Lau 	"udp ip6 rt fwd",
742c803475fSMartin KaFai Lau };
743c803475fSMartin KaFai Lau 
dtime_cnt_str(int test,int cnt)744c803475fSMartin KaFai Lau static const char *dtime_cnt_str(int test, int cnt)
745c803475fSMartin KaFai Lau {
746c803475fSMartin KaFai Lau 	static char name[64];
747c803475fSMartin KaFai Lau 
748c803475fSMartin KaFai Lau 	snprintf(name, sizeof(name), "%s %s", test_names[test], cnt_names[cnt]);
749c803475fSMartin KaFai Lau 
750c803475fSMartin KaFai Lau 	return name;
751c803475fSMartin KaFai Lau }
752c803475fSMartin KaFai Lau 
dtime_err_str(int test,int cnt)753c803475fSMartin KaFai Lau static const char *dtime_err_str(int test, int cnt)
754c803475fSMartin KaFai Lau {
755c803475fSMartin KaFai Lau 	static char name[64];
756c803475fSMartin KaFai Lau 
757c803475fSMartin KaFai Lau 	snprintf(name, sizeof(name), "%s %s errs", test_names[test],
758c803475fSMartin KaFai Lau 		 cnt_names[cnt]);
759c803475fSMartin KaFai Lau 
760c803475fSMartin KaFai Lau 	return name;
761c803475fSMartin KaFai Lau }
762c803475fSMartin KaFai Lau 
test_tcp_clear_dtime(struct test_tc_dtime * skel)763c803475fSMartin KaFai Lau static void test_tcp_clear_dtime(struct test_tc_dtime *skel)
764c803475fSMartin KaFai Lau {
765c803475fSMartin KaFai Lau 	int i, t = TCP_IP6_CLEAR_DTIME;
766c803475fSMartin KaFai Lau 	__u32 *dtimes = skel->bss->dtimes[t];
767c803475fSMartin KaFai Lau 	__u32 *errs = skel->bss->errs[t];
768c803475fSMartin KaFai Lau 
769c803475fSMartin KaFai Lau 	skel->bss->test = t;
770e6ff92f4SMartin KaFai Lau 	test_inet_dtime(AF_INET6, SOCK_STREAM, IP6_DST, 50000 + t);
771c803475fSMartin KaFai Lau 
772c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0,
773c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_FWDNS_P100));
774c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[INGRESS_FWDNS_P101], 0,
775c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_FWDNS_P101));
776c803475fSMartin KaFai Lau 	ASSERT_GT(dtimes[EGRESS_FWDNS_P100], 0,
777c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, EGRESS_FWDNS_P100));
778c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[EGRESS_FWDNS_P101], 0,
779c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, EGRESS_FWDNS_P101));
780c803475fSMartin KaFai Lau 	ASSERT_GT(dtimes[EGRESS_ENDHOST], 0,
781c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, EGRESS_ENDHOST));
782c803475fSMartin KaFai Lau 	ASSERT_GT(dtimes[INGRESS_ENDHOST], 0,
783c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_ENDHOST));
784c803475fSMartin KaFai Lau 
785c803475fSMartin KaFai Lau 	for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++)
786c803475fSMartin KaFai Lau 		ASSERT_EQ(errs[i], 0, dtime_err_str(t, i));
787c803475fSMartin KaFai Lau }
788c803475fSMartin KaFai Lau 
test_tcp_dtime(struct test_tc_dtime * skel,int family,bool bpf_fwd)789c803475fSMartin KaFai Lau static void test_tcp_dtime(struct test_tc_dtime *skel, int family, bool bpf_fwd)
790c803475fSMartin KaFai Lau {
791c803475fSMartin KaFai Lau 	__u32 *dtimes, *errs;
792c803475fSMartin KaFai Lau 	const char *addr;
793c803475fSMartin KaFai Lau 	int i, t;
794c803475fSMartin KaFai Lau 
795c803475fSMartin KaFai Lau 	if (family == AF_INET) {
796c803475fSMartin KaFai Lau 		t = bpf_fwd ? TCP_IP4 : TCP_IP4_RT_FWD;
797c803475fSMartin KaFai Lau 		addr = IP4_DST;
798c803475fSMartin KaFai Lau 	} else {
799c803475fSMartin KaFai Lau 		t = bpf_fwd ? TCP_IP6 : TCP_IP6_RT_FWD;
800c803475fSMartin KaFai Lau 		addr = IP6_DST;
801c803475fSMartin KaFai Lau 	}
802c803475fSMartin KaFai Lau 
803c803475fSMartin KaFai Lau 	dtimes = skel->bss->dtimes[t];
804c803475fSMartin KaFai Lau 	errs = skel->bss->errs[t];
805c803475fSMartin KaFai Lau 
806c803475fSMartin KaFai Lau 	skel->bss->test = t;
807e6ff92f4SMartin KaFai Lau 	test_inet_dtime(family, SOCK_STREAM, addr, 50000 + t);
808c803475fSMartin KaFai Lau 
809c803475fSMartin KaFai Lau 	/* fwdns_prio100 prog does not read delivery_time_type, so
810c803475fSMartin KaFai Lau 	 * kernel puts the (rcv) timetamp in __sk_buff->tstamp
811c803475fSMartin KaFai Lau 	 */
812c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0,
813c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_FWDNS_P100));
814c803475fSMartin KaFai Lau 	for (i = INGRESS_FWDNS_P101; i < SET_DTIME; i++)
815c803475fSMartin KaFai Lau 		ASSERT_GT(dtimes[i], 0, dtime_cnt_str(t, i));
816c803475fSMartin KaFai Lau 
817c803475fSMartin KaFai Lau 	for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++)
818c803475fSMartin KaFai Lau 		ASSERT_EQ(errs[i], 0, dtime_err_str(t, i));
819c803475fSMartin KaFai Lau }
820c803475fSMartin KaFai Lau 
test_udp_dtime(struct test_tc_dtime * skel,int family,bool bpf_fwd)821c803475fSMartin KaFai Lau static void test_udp_dtime(struct test_tc_dtime *skel, int family, bool bpf_fwd)
822c803475fSMartin KaFai Lau {
823c803475fSMartin KaFai Lau 	__u32 *dtimes, *errs;
824c803475fSMartin KaFai Lau 	const char *addr;
825c803475fSMartin KaFai Lau 	int i, t;
826c803475fSMartin KaFai Lau 
827c803475fSMartin KaFai Lau 	if (family == AF_INET) {
828c803475fSMartin KaFai Lau 		t = bpf_fwd ? UDP_IP4 : UDP_IP4_RT_FWD;
829c803475fSMartin KaFai Lau 		addr = IP4_DST;
830c803475fSMartin KaFai Lau 	} else {
831c803475fSMartin KaFai Lau 		t = bpf_fwd ? UDP_IP6 : UDP_IP6_RT_FWD;
832c803475fSMartin KaFai Lau 		addr = IP6_DST;
833c803475fSMartin KaFai Lau 	}
834c803475fSMartin KaFai Lau 
835c803475fSMartin KaFai Lau 	dtimes = skel->bss->dtimes[t];
836c803475fSMartin KaFai Lau 	errs = skel->bss->errs[t];
837c803475fSMartin KaFai Lau 
838c803475fSMartin KaFai Lau 	skel->bss->test = t;
839e6ff92f4SMartin KaFai Lau 	test_inet_dtime(family, SOCK_DGRAM, addr, 50000 + t);
840c803475fSMartin KaFai Lau 
841c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[INGRESS_FWDNS_P100], 0,
842c803475fSMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_FWDNS_P100));
843c803475fSMartin KaFai Lau 	/* non mono delivery time is not forwarded */
844c803475fSMartin KaFai Lau 	ASSERT_EQ(dtimes[INGRESS_FWDNS_P101], 0,
845e6ff92f4SMartin KaFai Lau 		  dtime_cnt_str(t, INGRESS_FWDNS_P101));
846c803475fSMartin KaFai Lau 	for (i = EGRESS_FWDNS_P100; i < SET_DTIME; i++)
847c803475fSMartin KaFai Lau 		ASSERT_GT(dtimes[i], 0, dtime_cnt_str(t, i));
848c803475fSMartin KaFai Lau 
849c803475fSMartin KaFai Lau 	for (i = INGRESS_FWDNS_P100; i < __MAX_CNT; i++)
850c803475fSMartin KaFai Lau 		ASSERT_EQ(errs[i], 0, dtime_err_str(t, i));
851c803475fSMartin KaFai Lau }
852c803475fSMartin KaFai Lau 
test_tc_redirect_dtime(struct netns_setup_result * setup_result)853c803475fSMartin KaFai Lau static void test_tc_redirect_dtime(struct netns_setup_result *setup_result)
854c803475fSMartin KaFai Lau {
855c803475fSMartin KaFai Lau 	struct test_tc_dtime *skel;
856c803475fSMartin KaFai Lau 	struct nstoken *nstoken;
857*a9800dc6SMartin KaFai Lau 	int hold_tstamp_fd, err;
858*a9800dc6SMartin KaFai Lau 
859*a9800dc6SMartin KaFai Lau 	/* Hold a sk with the SOCK_TIMESTAMP set to ensure there
860*a9800dc6SMartin KaFai Lau 	 * is no delay in the kernel net_enable_timestamp().
861*a9800dc6SMartin KaFai Lau 	 * This ensures the following tests must have
862*a9800dc6SMartin KaFai Lau 	 * non zero rcv tstamp in the recvmsg().
863*a9800dc6SMartin KaFai Lau 	 */
864*a9800dc6SMartin KaFai Lau 	hold_tstamp_fd = wait_netstamp_needed_key();
865*a9800dc6SMartin KaFai Lau 	if (!ASSERT_GE(hold_tstamp_fd, 0, "wait_netstamp_needed_key"))
866*a9800dc6SMartin KaFai Lau 		return;
867c803475fSMartin KaFai Lau 
868c803475fSMartin KaFai Lau 	skel = test_tc_dtime__open();
869c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(skel, "test_tc_dtime__open"))
870*a9800dc6SMartin KaFai Lau 		goto done;
871c803475fSMartin KaFai Lau 
87272f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_SRC = setup_result->ifindex_src_fwd;
87372f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_DST = setup_result->ifindex_dst_fwd;
874c803475fSMartin KaFai Lau 
875c803475fSMartin KaFai Lau 	err = test_tc_dtime__load(skel);
876c803475fSMartin KaFai Lau 	if (!ASSERT_OK(err, "test_tc_dtime__load"))
877c803475fSMartin KaFai Lau 		goto done;
878c803475fSMartin KaFai Lau 
87957d0863fSMartin KaFai Lau 	if (netns_load_dtime_bpf(skel, setup_result))
880c803475fSMartin KaFai Lau 		goto done;
881c803475fSMartin KaFai Lau 
882c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_FWD);
883c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
884c803475fSMartin KaFai Lau 		goto done;
885c803475fSMartin KaFai Lau 	err = set_forwarding(false);
886c803475fSMartin KaFai Lau 	close_netns(nstoken);
887c803475fSMartin KaFai Lau 	if (!ASSERT_OK(err, "disable forwarding"))
888c803475fSMartin KaFai Lau 		goto done;
889c803475fSMartin KaFai Lau 
890c803475fSMartin KaFai Lau 	test_tcp_clear_dtime(skel);
891c803475fSMartin KaFai Lau 
892c803475fSMartin KaFai Lau 	test_tcp_dtime(skel, AF_INET, true);
893c803475fSMartin KaFai Lau 	test_tcp_dtime(skel, AF_INET6, true);
894c803475fSMartin KaFai Lau 	test_udp_dtime(skel, AF_INET, true);
895c803475fSMartin KaFai Lau 	test_udp_dtime(skel, AF_INET6, true);
896c803475fSMartin KaFai Lau 
897c803475fSMartin KaFai Lau 	/* Test the kernel ip[6]_forward path instead
898c803475fSMartin KaFai Lau 	 * of bpf_redirect_neigh().
899c803475fSMartin KaFai Lau 	 */
900c803475fSMartin KaFai Lau 	nstoken = open_netns(NS_FWD);
901c803475fSMartin KaFai Lau 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
902c803475fSMartin KaFai Lau 		goto done;
903c803475fSMartin KaFai Lau 	err = set_forwarding(true);
904c803475fSMartin KaFai Lau 	close_netns(nstoken);
905c803475fSMartin KaFai Lau 	if (!ASSERT_OK(err, "enable forwarding"))
906c803475fSMartin KaFai Lau 		goto done;
907c803475fSMartin KaFai Lau 
908c803475fSMartin KaFai Lau 	test_tcp_dtime(skel, AF_INET, false);
909c803475fSMartin KaFai Lau 	test_tcp_dtime(skel, AF_INET6, false);
910c803475fSMartin KaFai Lau 	test_udp_dtime(skel, AF_INET, false);
911c803475fSMartin KaFai Lau 	test_udp_dtime(skel, AF_INET6, false);
912c803475fSMartin KaFai Lau 
913c803475fSMartin KaFai Lau done:
914c803475fSMartin KaFai Lau 	test_tc_dtime__destroy(skel);
915*a9800dc6SMartin KaFai Lau 	close(hold_tstamp_fd);
916c803475fSMartin KaFai Lau }
917c803475fSMartin KaFai Lau 
test_tc_redirect_neigh_fib(struct netns_setup_result * setup_result)918096eccdeSJussi Maki static void test_tc_redirect_neigh_fib(struct netns_setup_result *setup_result)
919096eccdeSJussi Maki {
9206fd5fb63SJussi Maki 	struct nstoken *nstoken = NULL;
9216fd5fb63SJussi Maki 	struct test_tc_neigh_fib *skel = NULL;
922096eccdeSJussi Maki 
9236fd5fb63SJussi Maki 	nstoken = open_netns(NS_FWD);
9246fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
9256fd5fb63SJussi Maki 		return;
9266fd5fb63SJussi Maki 
927096eccdeSJussi Maki 	skel = test_tc_neigh_fib__open();
928096eccdeSJussi Maki 	if (!ASSERT_OK_PTR(skel, "test_tc_neigh_fib__open"))
9296fd5fb63SJussi Maki 		goto done;
930096eccdeSJussi Maki 
9316fd5fb63SJussi Maki 	if (!ASSERT_OK(test_tc_neigh_fib__load(skel), "test_tc_neigh_fib__load"))
9326fd5fb63SJussi Maki 		goto done;
933096eccdeSJussi Maki 
9345dc42a7fSMartin KaFai Lau 	if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst,
9355dc42a7fSMartin KaFai Lau 			   skel->progs.tc_chk, setup_result))
936096eccdeSJussi Maki 		goto done;
937096eccdeSJussi Maki 
938096eccdeSJussi Maki 	/* bpf_fib_lookup() checks if forwarding is enabled */
9396fd5fb63SJussi Maki 	if (!ASSERT_OK(set_forwarding(true), "enable forwarding"))
940096eccdeSJussi Maki 		goto done;
941096eccdeSJussi Maki 
942096eccdeSJussi Maki 	test_connectivity();
9436fd5fb63SJussi Maki 
944096eccdeSJussi Maki done:
9456fd5fb63SJussi Maki 	if (skel)
946096eccdeSJussi Maki 		test_tc_neigh_fib__destroy(skel);
9476fd5fb63SJussi Maki 	close_netns(nstoken);
948096eccdeSJussi Maki }
949096eccdeSJussi Maki 
test_tc_redirect_neigh(struct netns_setup_result * setup_result)950096eccdeSJussi Maki static void test_tc_redirect_neigh(struct netns_setup_result *setup_result)
951096eccdeSJussi Maki {
9526fd5fb63SJussi Maki 	struct nstoken *nstoken = NULL;
9536fd5fb63SJussi Maki 	struct test_tc_neigh *skel = NULL;
954096eccdeSJussi Maki 	int err;
955096eccdeSJussi Maki 
9566fd5fb63SJussi Maki 	nstoken = open_netns(NS_FWD);
9576fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
9586fd5fb63SJussi Maki 		return;
9596fd5fb63SJussi Maki 
960096eccdeSJussi Maki 	skel = test_tc_neigh__open();
961096eccdeSJussi Maki 	if (!ASSERT_OK_PTR(skel, "test_tc_neigh__open"))
9626fd5fb63SJussi Maki 		goto done;
963096eccdeSJussi Maki 
96472f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_SRC = setup_result->ifindex_src_fwd;
96572f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_DST = setup_result->ifindex_dst_fwd;
966096eccdeSJussi Maki 
967096eccdeSJussi Maki 	err = test_tc_neigh__load(skel);
9686fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "test_tc_neigh__load"))
9696fd5fb63SJussi Maki 		goto done;
970096eccdeSJussi Maki 
9715dc42a7fSMartin KaFai Lau 	if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst,
9725dc42a7fSMartin KaFai Lau 			   skel->progs.tc_chk, setup_result))
973096eccdeSJussi Maki 		goto done;
974096eccdeSJussi Maki 
9756fd5fb63SJussi Maki 	if (!ASSERT_OK(set_forwarding(false), "disable forwarding"))
9766fd5fb63SJussi Maki 		goto done;
9776fd5fb63SJussi Maki 
978096eccdeSJussi Maki 	test_connectivity();
979096eccdeSJussi Maki 
980096eccdeSJussi Maki done:
9816fd5fb63SJussi Maki 	if (skel)
982096eccdeSJussi Maki 		test_tc_neigh__destroy(skel);
9836fd5fb63SJussi Maki 	close_netns(nstoken);
984096eccdeSJussi Maki }
985096eccdeSJussi Maki 
test_tc_redirect_peer(struct netns_setup_result * setup_result)986096eccdeSJussi Maki static void test_tc_redirect_peer(struct netns_setup_result *setup_result)
987096eccdeSJussi Maki {
9886fd5fb63SJussi Maki 	struct nstoken *nstoken;
989096eccdeSJussi Maki 	struct test_tc_peer *skel;
990096eccdeSJussi Maki 	int err;
991096eccdeSJussi Maki 
9926fd5fb63SJussi Maki 	nstoken = open_netns(NS_FWD);
9936fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns fwd"))
9946fd5fb63SJussi Maki 		return;
9956fd5fb63SJussi Maki 
996096eccdeSJussi Maki 	skel = test_tc_peer__open();
997096eccdeSJussi Maki 	if (!ASSERT_OK_PTR(skel, "test_tc_peer__open"))
9986fd5fb63SJussi Maki 		goto done;
999096eccdeSJussi Maki 
100072f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_SRC = setup_result->ifindex_src_fwd;
100172f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_DST = setup_result->ifindex_dst_fwd;
1002096eccdeSJussi Maki 
1003096eccdeSJussi Maki 	err = test_tc_peer__load(skel);
10046fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "test_tc_peer__load"))
10056fd5fb63SJussi Maki 		goto done;
1006096eccdeSJussi Maki 
10075dc42a7fSMartin KaFai Lau 	if (netns_load_bpf(skel->progs.tc_src, skel->progs.tc_dst,
10085dc42a7fSMartin KaFai Lau 			   skel->progs.tc_chk, setup_result))
1009096eccdeSJussi Maki 		goto done;
1010096eccdeSJussi Maki 
10116fd5fb63SJussi Maki 	if (!ASSERT_OK(set_forwarding(false), "disable forwarding"))
10126fd5fb63SJussi Maki 		goto done;
10136fd5fb63SJussi Maki 
1014096eccdeSJussi Maki 	test_connectivity();
1015096eccdeSJussi Maki 
1016096eccdeSJussi Maki done:
10176fd5fb63SJussi Maki 	if (skel)
1018096eccdeSJussi Maki 		test_tc_peer__destroy(skel);
10196fd5fb63SJussi Maki 	close_netns(nstoken);
10206fd5fb63SJussi Maki }
10216fd5fb63SJussi Maki 
tun_open(char * name)10226fd5fb63SJussi Maki static int tun_open(char *name)
10236fd5fb63SJussi Maki {
10246fd5fb63SJussi Maki 	struct ifreq ifr;
10256fd5fb63SJussi Maki 	int fd, err;
10266fd5fb63SJussi Maki 
10276fd5fb63SJussi Maki 	fd = open("/dev/net/tun", O_RDWR);
10286fd5fb63SJussi Maki 	if (!ASSERT_GE(fd, 0, "open /dev/net/tun"))
10296fd5fb63SJussi Maki 		return -1;
10306fd5fb63SJussi Maki 
10316fd5fb63SJussi Maki 	memset(&ifr, 0, sizeof(ifr));
10326fd5fb63SJussi Maki 
10336fd5fb63SJussi Maki 	ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
10346fd5fb63SJussi Maki 	if (*name)
10356fd5fb63SJussi Maki 		strncpy(ifr.ifr_name, name, IFNAMSIZ);
10366fd5fb63SJussi Maki 
10376fd5fb63SJussi Maki 	err = ioctl(fd, TUNSETIFF, &ifr);
10386fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "ioctl TUNSETIFF"))
10396fd5fb63SJussi Maki 		goto fail;
10406fd5fb63SJussi Maki 
1041b61987d3SHangbin Liu 	SYS(fail, "ip link set dev %s up", name);
10426fd5fb63SJussi Maki 
10436fd5fb63SJussi Maki 	return fd;
10446fd5fb63SJussi Maki fail:
10456fd5fb63SJussi Maki 	close(fd);
10466fd5fb63SJussi Maki 	return -1;
10476fd5fb63SJussi Maki }
10486fd5fb63SJussi Maki 
10496fd5fb63SJussi Maki enum {
10506fd5fb63SJussi Maki 	SRC_TO_TARGET = 0,
10516fd5fb63SJussi Maki 	TARGET_TO_SRC = 1,
10526fd5fb63SJussi Maki };
10536fd5fb63SJussi Maki 
tun_relay_loop(int src_fd,int target_fd)10546fd5fb63SJussi Maki static int tun_relay_loop(int src_fd, int target_fd)
10556fd5fb63SJussi Maki {
10566fd5fb63SJussi Maki 	fd_set rfds, wfds;
10576fd5fb63SJussi Maki 
10586fd5fb63SJussi Maki 	FD_ZERO(&rfds);
10596fd5fb63SJussi Maki 	FD_ZERO(&wfds);
10606fd5fb63SJussi Maki 
10616fd5fb63SJussi Maki 	for (;;) {
10626fd5fb63SJussi Maki 		char buf[1500];
10636fd5fb63SJussi Maki 		int direction, nread, nwrite;
10646fd5fb63SJussi Maki 
10656fd5fb63SJussi Maki 		FD_SET(src_fd, &rfds);
10666fd5fb63SJussi Maki 		FD_SET(target_fd, &rfds);
10676fd5fb63SJussi Maki 
10686fd5fb63SJussi Maki 		if (select(1 + MAX(src_fd, target_fd), &rfds, NULL, NULL, NULL) < 0) {
10696fd5fb63SJussi Maki 			log_err("select failed");
10706fd5fb63SJussi Maki 			return 1;
10716fd5fb63SJussi Maki 		}
10726fd5fb63SJussi Maki 
10736fd5fb63SJussi Maki 		direction = FD_ISSET(src_fd, &rfds) ? SRC_TO_TARGET : TARGET_TO_SRC;
10746fd5fb63SJussi Maki 
10756fd5fb63SJussi Maki 		nread = read(direction == SRC_TO_TARGET ? src_fd : target_fd, buf, sizeof(buf));
10766fd5fb63SJussi Maki 		if (nread < 0) {
10776fd5fb63SJussi Maki 			log_err("read failed");
10786fd5fb63SJussi Maki 			return 1;
10796fd5fb63SJussi Maki 		}
10806fd5fb63SJussi Maki 
10816fd5fb63SJussi Maki 		nwrite = write(direction == SRC_TO_TARGET ? target_fd : src_fd, buf, nread);
10826fd5fb63SJussi Maki 		if (nwrite != nread) {
10836fd5fb63SJussi Maki 			log_err("write failed");
10846fd5fb63SJussi Maki 			return 1;
10856fd5fb63SJussi Maki 		}
10866fd5fb63SJussi Maki 	}
10876fd5fb63SJussi Maki }
10886fd5fb63SJussi Maki 
test_tc_redirect_peer_l3(struct netns_setup_result * setup_result)10896fd5fb63SJussi Maki static void test_tc_redirect_peer_l3(struct netns_setup_result *setup_result)
10906fd5fb63SJussi Maki {
1091f1b73577SMartin KaFai Lau 	LIBBPF_OPTS(bpf_tc_hook, qdisc_tun_fwd);
109272f1ba02SDaniel Borkmann 	LIBBPF_OPTS(bpf_tc_hook, qdisc_dst_fwd);
10936fd5fb63SJussi Maki 	struct test_tc_peer *skel = NULL;
10946fd5fb63SJussi Maki 	struct nstoken *nstoken = NULL;
10956fd5fb63SJussi Maki 	int err;
10966fd5fb63SJussi Maki 	int tunnel_pid = -1;
1097ca21a3e5SYonghong Song 	int src_fd, target_fd = -1;
10986fd5fb63SJussi Maki 	int ifindex;
10996fd5fb63SJussi Maki 
11006fd5fb63SJussi Maki 	/* Start a L3 TUN/TAP tunnel between the src and dst namespaces.
11016fd5fb63SJussi Maki 	 * This test is using TUN/TAP instead of e.g. IPIP or GRE tunnel as those
11026fd5fb63SJussi Maki 	 * expose the L2 headers encapsulating the IP packet to BPF and hence
11036fd5fb63SJussi Maki 	 * don't have skb in suitable state for this test. Alternative to TUN/TAP
11046fd5fb63SJussi Maki 	 * would be e.g. Wireguard which would appear as a pure L3 device to BPF,
11056fd5fb63SJussi Maki 	 * but that requires much more complicated setup.
11066fd5fb63SJussi Maki 	 */
11076fd5fb63SJussi Maki 	nstoken = open_netns(NS_SRC);
11086fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns " NS_SRC))
11096fd5fb63SJussi Maki 		return;
11106fd5fb63SJussi Maki 
11116fd5fb63SJussi Maki 	src_fd = tun_open("tun_src");
11126fd5fb63SJussi Maki 	if (!ASSERT_GE(src_fd, 0, "tun_open tun_src"))
11136fd5fb63SJussi Maki 		goto fail;
11146fd5fb63SJussi Maki 
11156fd5fb63SJussi Maki 	close_netns(nstoken);
11166fd5fb63SJussi Maki 
11176fd5fb63SJussi Maki 	nstoken = open_netns(NS_FWD);
11186fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(nstoken, "setns " NS_FWD))
11196fd5fb63SJussi Maki 		goto fail;
11206fd5fb63SJussi Maki 
11216fd5fb63SJussi Maki 	target_fd = tun_open("tun_fwd");
11226fd5fb63SJussi Maki 	if (!ASSERT_GE(target_fd, 0, "tun_open tun_fwd"))
11236fd5fb63SJussi Maki 		goto fail;
11246fd5fb63SJussi Maki 
11256fd5fb63SJussi Maki 	tunnel_pid = fork();
11266fd5fb63SJussi Maki 	if (!ASSERT_GE(tunnel_pid, 0, "fork tun_relay_loop"))
11276fd5fb63SJussi Maki 		goto fail;
11286fd5fb63SJussi Maki 
11296fd5fb63SJussi Maki 	if (tunnel_pid == 0)
11306fd5fb63SJussi Maki 		exit(tun_relay_loop(src_fd, target_fd));
11316fd5fb63SJussi Maki 
11326fd5fb63SJussi Maki 	skel = test_tc_peer__open();
11336fd5fb63SJussi Maki 	if (!ASSERT_OK_PTR(skel, "test_tc_peer__open"))
11346fd5fb63SJussi Maki 		goto fail;
11356fd5fb63SJussi Maki 
1136052c82dcSMartin KaFai Lau 	ifindex = if_nametoindex("tun_fwd");
1137052c82dcSMartin KaFai Lau 	if (!ASSERT_GT(ifindex, 0, "if_indextoname tun_fwd"))
11386fd5fb63SJussi Maki 		goto fail;
11396fd5fb63SJussi Maki 
11406fd5fb63SJussi Maki 	skel->rodata->IFINDEX_SRC = ifindex;
114172f1ba02SDaniel Borkmann 	skel->rodata->IFINDEX_DST = setup_result->ifindex_dst_fwd;
11426fd5fb63SJussi Maki 
11436fd5fb63SJussi Maki 	err = test_tc_peer__load(skel);
11446fd5fb63SJussi Maki 	if (!ASSERT_OK(err, "test_tc_peer__load"))
11456fd5fb63SJussi Maki 		goto fail;
11466fd5fb63SJussi Maki 
11476fd5fb63SJussi Maki 	/* Load "tc_src_l3" to the tun_fwd interface to redirect packets
11486fd5fb63SJussi Maki 	 * towards dst, and "tc_dst" to redirect packets
114972f1ba02SDaniel Borkmann 	 * and "tc_chk" on dst_fwd to drop non-redirected packets.
11506fd5fb63SJussi Maki 	 */
1151f1b73577SMartin KaFai Lau 	/* tc qdisc add dev tun_fwd clsact */
1152f1b73577SMartin KaFai Lau 	QDISC_CLSACT_CREATE(&qdisc_tun_fwd, ifindex);
1153f1b73577SMartin KaFai Lau 	/* tc filter add dev tun_fwd ingress bpf da tc_src_l3 */
1154f1b73577SMartin KaFai Lau 	XGRESS_FILTER_ADD(&qdisc_tun_fwd, BPF_TC_INGRESS, skel->progs.tc_src_l3, 0);
11556fd5fb63SJussi Maki 
115672f1ba02SDaniel Borkmann 	/* tc qdisc add dev dst_fwd clsact */
115772f1ba02SDaniel Borkmann 	QDISC_CLSACT_CREATE(&qdisc_dst_fwd, setup_result->ifindex_dst_fwd);
115872f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd ingress bpf da tc_dst_l3 */
115972f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_INGRESS, skel->progs.tc_dst_l3, 0);
116072f1ba02SDaniel Borkmann 	/* tc filter add dev dst_fwd egress bpf da tc_chk */
116172f1ba02SDaniel Borkmann 	XGRESS_FILTER_ADD(&qdisc_dst_fwd, BPF_TC_EGRESS, skel->progs.tc_chk, 0);
11626fd5fb63SJussi Maki 
11636fd5fb63SJussi Maki 	/* Setup route and neigh tables */
1164b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_SRC " addr add dev tun_src " IP4_TUN_SRC "/24");
1165b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_FWD " addr add dev tun_fwd " IP4_TUN_FWD "/24");
11666fd5fb63SJussi Maki 
1167b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_SRC " addr add dev tun_src " IP6_TUN_SRC "/64 nodad");
1168b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_FWD " addr add dev tun_fwd " IP6_TUN_FWD "/64 nodad");
11696fd5fb63SJussi Maki 
117072f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_SRC " route del " IP4_DST "/32 dev src scope global");
1171b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_SRC " route add " IP4_DST "/32 via " IP4_TUN_FWD
11726fd5fb63SJussi Maki 	    " dev tun_src scope global");
117372f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_DST " route add " IP4_TUN_SRC "/32 dev dst scope global");
117472f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_SRC " route del " IP6_DST "/128 dev src scope global");
1175b61987d3SHangbin Liu 	SYS(fail, "ip -netns " NS_SRC " route add " IP6_DST "/128 via " IP6_TUN_FWD
11766fd5fb63SJussi Maki 	    " dev tun_src scope global");
117772f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_DST " route add " IP6_TUN_SRC "/128 dev dst scope global");
11786fd5fb63SJussi Maki 
117972f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_DST " neigh add " IP4_TUN_SRC " dev dst lladdr " MAC_DST_FWD);
118072f1ba02SDaniel Borkmann 	SYS(fail, "ip -netns " NS_DST " neigh add " IP6_TUN_SRC " dev dst lladdr " MAC_DST_FWD);
11816fd5fb63SJussi Maki 
11826fd5fb63SJussi Maki 	if (!ASSERT_OK(set_forwarding(false), "disable forwarding"))
11836fd5fb63SJussi Maki 		goto fail;
11846fd5fb63SJussi Maki 
11856fd5fb63SJussi Maki 	test_connectivity();
11866fd5fb63SJussi Maki 
11876fd5fb63SJussi Maki fail:
11886fd5fb63SJussi Maki 	if (tunnel_pid > 0) {
11896fd5fb63SJussi Maki 		kill(tunnel_pid, SIGTERM);
11906fd5fb63SJussi Maki 		waitpid(tunnel_pid, NULL, 0);
11916fd5fb63SJussi Maki 	}
11926fd5fb63SJussi Maki 	if (src_fd >= 0)
11936fd5fb63SJussi Maki 		close(src_fd);
11946fd5fb63SJussi Maki 	if (target_fd >= 0)
11956fd5fb63SJussi Maki 		close(target_fd);
11966fd5fb63SJussi Maki 	if (skel)
11976fd5fb63SJussi Maki 		test_tc_peer__destroy(skel);
11986fd5fb63SJussi Maki 	if (nstoken)
11996fd5fb63SJussi Maki 		close_netns(nstoken);
12006fd5fb63SJussi Maki }
12016fd5fb63SJussi Maki 
120272f1ba02SDaniel Borkmann #define RUN_TEST(name, mode)                                                                \
12036fd5fb63SJussi Maki 	({                                                                                  \
120472f1ba02SDaniel Borkmann 		struct netns_setup_result setup_result = { .dev_mode = mode, };             \
12056fd5fb63SJussi Maki 		if (test__start_subtest(#name))                                             \
12066fd5fb63SJussi Maki 			if (ASSERT_OK(netns_setup_namespaces("add"), "setup namespaces")) { \
12076fd5fb63SJussi Maki 				if (ASSERT_OK(netns_setup_links_and_routes(&setup_result),  \
12086fd5fb63SJussi Maki 					      "setup links and routes"))                    \
12096fd5fb63SJussi Maki 					test_ ## name(&setup_result);                       \
12106fd5fb63SJussi Maki 				netns_setup_namespaces("delete");                           \
12116fd5fb63SJussi Maki 			}                                                                   \
12126fd5fb63SJussi Maki 	})
12136fd5fb63SJussi Maki 
test_tc_redirect_run_tests(void * arg)12146fd5fb63SJussi Maki static void *test_tc_redirect_run_tests(void *arg)
12156fd5fb63SJussi Maki {
1216e1ef62a4SYucong Sun 	netns_setup_namespaces_nofail("delete");
1217e1ef62a4SYucong Sun 
121872f1ba02SDaniel Borkmann 	RUN_TEST(tc_redirect_peer, MODE_VETH);
121972f1ba02SDaniel Borkmann 	RUN_TEST(tc_redirect_peer_l3, MODE_VETH);
122072f1ba02SDaniel Borkmann 	RUN_TEST(tc_redirect_neigh, MODE_VETH);
122172f1ba02SDaniel Borkmann 	RUN_TEST(tc_redirect_neigh_fib, MODE_VETH);
122272f1ba02SDaniel Borkmann 	RUN_TEST(tc_redirect_dtime, MODE_VETH);
12236fd5fb63SJussi Maki 	return NULL;
1224096eccdeSJussi Maki }
1225096eccdeSJussi Maki 
test_tc_redirect(void)12269b6a7773SMartin KaFai Lau void test_tc_redirect(void)
1227096eccdeSJussi Maki {
12286fd5fb63SJussi Maki 	pthread_t test_thread;
12296fd5fb63SJussi Maki 	int err;
1230096eccdeSJussi Maki 
12316fd5fb63SJussi Maki 	/* Run the tests in their own thread to isolate the namespace changes
12326fd5fb63SJussi Maki 	 * so they do not affect the environment of other tests.
12336fd5fb63SJussi Maki 	 * (specifically needed because of unshare(CLONE_NEWNS) in open_netns())
12346fd5fb63SJussi Maki 	 */
12356fd5fb63SJussi Maki 	err = pthread_create(&test_thread, NULL, &test_tc_redirect_run_tests, NULL);
12366fd5fb63SJussi Maki 	if (ASSERT_OK(err, "pthread_create"))
12376fd5fb63SJussi Maki 		ASSERT_OK(pthread_join(test_thread, NULL), "pthread_join");
1238096eccdeSJussi Maki }
1239