1 // SPDX-License-Identifier: GPL-2.0
2 #include <test_progs.h>
3 #include <network_helpers.h>
4 #include <error.h>
5 #include <linux/if.h>
6 #include <linux/if_tun.h>
7 #include <sys/uio.h>
8 
9 #include "bpf_flow.skel.h"
10 
11 #ifndef IP_MF
12 #define IP_MF 0x2000
13 #endif
14 
15 #define CHECK_FLOW_KEYS(desc, got, expected)				\
16 	_CHECK(memcmp(&got, &expected, sizeof(got)) != 0,		\
17 	      desc,							\
18 	      topts.duration,						\
19 	      "nhoff=%u/%u "						\
20 	      "thoff=%u/%u "						\
21 	      "addr_proto=0x%x/0x%x "					\
22 	      "is_frag=%u/%u "						\
23 	      "is_first_frag=%u/%u "					\
24 	      "is_encap=%u/%u "						\
25 	      "ip_proto=0x%x/0x%x "					\
26 	      "n_proto=0x%x/0x%x "					\
27 	      "flow_label=0x%x/0x%x "					\
28 	      "sport=%u/%u "						\
29 	      "dport=%u/%u\n",						\
30 	      got.nhoff, expected.nhoff,				\
31 	      got.thoff, expected.thoff,				\
32 	      got.addr_proto, expected.addr_proto,			\
33 	      got.is_frag, expected.is_frag,				\
34 	      got.is_first_frag, expected.is_first_frag,		\
35 	      got.is_encap, expected.is_encap,				\
36 	      got.ip_proto, expected.ip_proto,				\
37 	      got.n_proto, expected.n_proto,				\
38 	      got.flow_label, expected.flow_label,			\
39 	      got.sport, expected.sport,				\
40 	      got.dport, expected.dport)
41 
42 struct ipv4_pkt {
43 	struct ethhdr eth;
44 	struct iphdr iph;
45 	struct tcphdr tcp;
46 } __packed;
47 
48 struct ipip_pkt {
49 	struct ethhdr eth;
50 	struct iphdr iph;
51 	struct iphdr iph_inner;
52 	struct tcphdr tcp;
53 } __packed;
54 
55 struct svlan_ipv4_pkt {
56 	struct ethhdr eth;
57 	__u16 vlan_tci;
58 	__u16 vlan_proto;
59 	struct iphdr iph;
60 	struct tcphdr tcp;
61 } __packed;
62 
63 struct ipv6_pkt {
64 	struct ethhdr eth;
65 	struct ipv6hdr iph;
66 	struct tcphdr tcp;
67 } __packed;
68 
69 struct ipv6_frag_pkt {
70 	struct ethhdr eth;
71 	struct ipv6hdr iph;
72 	struct frag_hdr {
73 		__u8 nexthdr;
74 		__u8 reserved;
75 		__be16 frag_off;
76 		__be32 identification;
77 	} ipf;
78 	struct tcphdr tcp;
79 } __packed;
80 
81 struct dvlan_ipv6_pkt {
82 	struct ethhdr eth;
83 	__u16 vlan_tci;
84 	__u16 vlan_proto;
85 	__u16 vlan_tci2;
86 	__u16 vlan_proto2;
87 	struct ipv6hdr iph;
88 	struct tcphdr tcp;
89 } __packed;
90 
91 struct test {
92 	const char *name;
93 	union {
94 		struct ipv4_pkt ipv4;
95 		struct svlan_ipv4_pkt svlan_ipv4;
96 		struct ipip_pkt ipip;
97 		struct ipv6_pkt ipv6;
98 		struct ipv6_frag_pkt ipv6_frag;
99 		struct dvlan_ipv6_pkt dvlan_ipv6;
100 	} pkt;
101 	struct bpf_flow_keys keys;
102 	__u32 flags;
103 };
104 
105 #define VLAN_HLEN	4
106 
107 static __u32 duration;
108 struct test tests[] = {
109 	{
110 		.name = "ipv4",
111 		.pkt.ipv4 = {
112 			.eth.h_proto = __bpf_constant_htons(ETH_P_IP),
113 			.iph.ihl = 5,
114 			.iph.protocol = IPPROTO_TCP,
115 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
116 			.tcp.doff = 5,
117 			.tcp.source = 80,
118 			.tcp.dest = 8080,
119 		},
120 		.keys = {
121 			.nhoff = ETH_HLEN,
122 			.thoff = ETH_HLEN + sizeof(struct iphdr),
123 			.addr_proto = ETH_P_IP,
124 			.ip_proto = IPPROTO_TCP,
125 			.n_proto = __bpf_constant_htons(ETH_P_IP),
126 			.sport = 80,
127 			.dport = 8080,
128 		},
129 	},
130 	{
131 		.name = "ipv6",
132 		.pkt.ipv6 = {
133 			.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
134 			.iph.nexthdr = IPPROTO_TCP,
135 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
136 			.tcp.doff = 5,
137 			.tcp.source = 80,
138 			.tcp.dest = 8080,
139 		},
140 		.keys = {
141 			.nhoff = ETH_HLEN,
142 			.thoff = ETH_HLEN + sizeof(struct ipv6hdr),
143 			.addr_proto = ETH_P_IPV6,
144 			.ip_proto = IPPROTO_TCP,
145 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
146 			.sport = 80,
147 			.dport = 8080,
148 		},
149 	},
150 	{
151 		.name = "802.1q-ipv4",
152 		.pkt.svlan_ipv4 = {
153 			.eth.h_proto = __bpf_constant_htons(ETH_P_8021Q),
154 			.vlan_proto = __bpf_constant_htons(ETH_P_IP),
155 			.iph.ihl = 5,
156 			.iph.protocol = IPPROTO_TCP,
157 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
158 			.tcp.doff = 5,
159 			.tcp.source = 80,
160 			.tcp.dest = 8080,
161 		},
162 		.keys = {
163 			.nhoff = ETH_HLEN + VLAN_HLEN,
164 			.thoff = ETH_HLEN + VLAN_HLEN + sizeof(struct iphdr),
165 			.addr_proto = ETH_P_IP,
166 			.ip_proto = IPPROTO_TCP,
167 			.n_proto = __bpf_constant_htons(ETH_P_IP),
168 			.sport = 80,
169 			.dport = 8080,
170 		},
171 	},
172 	{
173 		.name = "802.1ad-ipv6",
174 		.pkt.dvlan_ipv6 = {
175 			.eth.h_proto = __bpf_constant_htons(ETH_P_8021AD),
176 			.vlan_proto = __bpf_constant_htons(ETH_P_8021Q),
177 			.vlan_proto2 = __bpf_constant_htons(ETH_P_IPV6),
178 			.iph.nexthdr = IPPROTO_TCP,
179 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
180 			.tcp.doff = 5,
181 			.tcp.source = 80,
182 			.tcp.dest = 8080,
183 		},
184 		.keys = {
185 			.nhoff = ETH_HLEN + VLAN_HLEN * 2,
186 			.thoff = ETH_HLEN + VLAN_HLEN * 2 +
187 				sizeof(struct ipv6hdr),
188 			.addr_proto = ETH_P_IPV6,
189 			.ip_proto = IPPROTO_TCP,
190 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
191 			.sport = 80,
192 			.dport = 8080,
193 		},
194 	},
195 	{
196 		.name = "ipv4-frag",
197 		.pkt.ipv4 = {
198 			.eth.h_proto = __bpf_constant_htons(ETH_P_IP),
199 			.iph.ihl = 5,
200 			.iph.protocol = IPPROTO_TCP,
201 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
202 			.iph.frag_off = __bpf_constant_htons(IP_MF),
203 			.tcp.doff = 5,
204 			.tcp.source = 80,
205 			.tcp.dest = 8080,
206 		},
207 		.keys = {
208 			.flags = BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG,
209 			.nhoff = ETH_HLEN,
210 			.thoff = ETH_HLEN + sizeof(struct iphdr),
211 			.addr_proto = ETH_P_IP,
212 			.ip_proto = IPPROTO_TCP,
213 			.n_proto = __bpf_constant_htons(ETH_P_IP),
214 			.is_frag = true,
215 			.is_first_frag = true,
216 			.sport = 80,
217 			.dport = 8080,
218 		},
219 		.flags = BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG,
220 	},
221 	{
222 		.name = "ipv4-no-frag",
223 		.pkt.ipv4 = {
224 			.eth.h_proto = __bpf_constant_htons(ETH_P_IP),
225 			.iph.ihl = 5,
226 			.iph.protocol = IPPROTO_TCP,
227 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
228 			.iph.frag_off = __bpf_constant_htons(IP_MF),
229 			.tcp.doff = 5,
230 			.tcp.source = 80,
231 			.tcp.dest = 8080,
232 		},
233 		.keys = {
234 			.nhoff = ETH_HLEN,
235 			.thoff = ETH_HLEN + sizeof(struct iphdr),
236 			.addr_proto = ETH_P_IP,
237 			.ip_proto = IPPROTO_TCP,
238 			.n_proto = __bpf_constant_htons(ETH_P_IP),
239 			.is_frag = true,
240 			.is_first_frag = true,
241 		},
242 	},
243 	{
244 		.name = "ipv6-frag",
245 		.pkt.ipv6_frag = {
246 			.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
247 			.iph.nexthdr = IPPROTO_FRAGMENT,
248 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
249 			.ipf.nexthdr = IPPROTO_TCP,
250 			.tcp.doff = 5,
251 			.tcp.source = 80,
252 			.tcp.dest = 8080,
253 		},
254 		.keys = {
255 			.flags = BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG,
256 			.nhoff = ETH_HLEN,
257 			.thoff = ETH_HLEN + sizeof(struct ipv6hdr) +
258 				sizeof(struct frag_hdr),
259 			.addr_proto = ETH_P_IPV6,
260 			.ip_proto = IPPROTO_TCP,
261 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
262 			.is_frag = true,
263 			.is_first_frag = true,
264 			.sport = 80,
265 			.dport = 8080,
266 		},
267 		.flags = BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG,
268 	},
269 	{
270 		.name = "ipv6-no-frag",
271 		.pkt.ipv6_frag = {
272 			.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
273 			.iph.nexthdr = IPPROTO_FRAGMENT,
274 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
275 			.ipf.nexthdr = IPPROTO_TCP,
276 			.tcp.doff = 5,
277 			.tcp.source = 80,
278 			.tcp.dest = 8080,
279 		},
280 		.keys = {
281 			.nhoff = ETH_HLEN,
282 			.thoff = ETH_HLEN + sizeof(struct ipv6hdr) +
283 				sizeof(struct frag_hdr),
284 			.addr_proto = ETH_P_IPV6,
285 			.ip_proto = IPPROTO_TCP,
286 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
287 			.is_frag = true,
288 			.is_first_frag = true,
289 		},
290 	},
291 	{
292 		.name = "ipv6-flow-label",
293 		.pkt.ipv6 = {
294 			.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
295 			.iph.nexthdr = IPPROTO_TCP,
296 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
297 			.iph.flow_lbl = { 0xb, 0xee, 0xef },
298 			.tcp.doff = 5,
299 			.tcp.source = 80,
300 			.tcp.dest = 8080,
301 		},
302 		.keys = {
303 			.nhoff = ETH_HLEN,
304 			.thoff = ETH_HLEN + sizeof(struct ipv6hdr),
305 			.addr_proto = ETH_P_IPV6,
306 			.ip_proto = IPPROTO_TCP,
307 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
308 			.sport = 80,
309 			.dport = 8080,
310 			.flow_label = __bpf_constant_htonl(0xbeeef),
311 		},
312 	},
313 	{
314 		.name = "ipv6-no-flow-label",
315 		.pkt.ipv6 = {
316 			.eth.h_proto = __bpf_constant_htons(ETH_P_IPV6),
317 			.iph.nexthdr = IPPROTO_TCP,
318 			.iph.payload_len = __bpf_constant_htons(MAGIC_BYTES),
319 			.iph.flow_lbl = { 0xb, 0xee, 0xef },
320 			.tcp.doff = 5,
321 			.tcp.source = 80,
322 			.tcp.dest = 8080,
323 		},
324 		.keys = {
325 			.flags = BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL,
326 			.nhoff = ETH_HLEN,
327 			.thoff = ETH_HLEN + sizeof(struct ipv6hdr),
328 			.addr_proto = ETH_P_IPV6,
329 			.ip_proto = IPPROTO_TCP,
330 			.n_proto = __bpf_constant_htons(ETH_P_IPV6),
331 			.flow_label = __bpf_constant_htonl(0xbeeef),
332 		},
333 		.flags = BPF_FLOW_DISSECTOR_F_STOP_AT_FLOW_LABEL,
334 	},
335 	{
336 		.name = "ipip-encap",
337 		.pkt.ipip = {
338 			.eth.h_proto = __bpf_constant_htons(ETH_P_IP),
339 			.iph.ihl = 5,
340 			.iph.protocol = IPPROTO_IPIP,
341 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
342 			.iph_inner.ihl = 5,
343 			.iph_inner.protocol = IPPROTO_TCP,
344 			.iph_inner.tot_len =
345 				__bpf_constant_htons(MAGIC_BYTES) -
346 				sizeof(struct iphdr),
347 			.tcp.doff = 5,
348 			.tcp.source = 80,
349 			.tcp.dest = 8080,
350 		},
351 		.keys = {
352 			.nhoff = ETH_HLEN,
353 			.thoff = ETH_HLEN + sizeof(struct iphdr) +
354 				sizeof(struct iphdr),
355 			.addr_proto = ETH_P_IP,
356 			.ip_proto = IPPROTO_TCP,
357 			.n_proto = __bpf_constant_htons(ETH_P_IP),
358 			.is_encap = true,
359 			.sport = 80,
360 			.dport = 8080,
361 		},
362 	},
363 	{
364 		.name = "ipip-no-encap",
365 		.pkt.ipip = {
366 			.eth.h_proto = __bpf_constant_htons(ETH_P_IP),
367 			.iph.ihl = 5,
368 			.iph.protocol = IPPROTO_IPIP,
369 			.iph.tot_len = __bpf_constant_htons(MAGIC_BYTES),
370 			.iph_inner.ihl = 5,
371 			.iph_inner.protocol = IPPROTO_TCP,
372 			.iph_inner.tot_len =
373 				__bpf_constant_htons(MAGIC_BYTES) -
374 				sizeof(struct iphdr),
375 			.tcp.doff = 5,
376 			.tcp.source = 80,
377 			.tcp.dest = 8080,
378 		},
379 		.keys = {
380 			.flags = BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP,
381 			.nhoff = ETH_HLEN,
382 			.thoff = ETH_HLEN + sizeof(struct iphdr),
383 			.addr_proto = ETH_P_IP,
384 			.ip_proto = IPPROTO_IPIP,
385 			.n_proto = __bpf_constant_htons(ETH_P_IP),
386 			.is_encap = true,
387 		},
388 		.flags = BPF_FLOW_DISSECTOR_F_STOP_AT_ENCAP,
389 	},
390 };
391 
392 static int create_tap(const char *ifname)
393 {
394 	struct ifreq ifr = {
395 		.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_NAPI | IFF_NAPI_FRAGS,
396 	};
397 	int fd, ret;
398 
399 	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
400 
401 	fd = open("/dev/net/tun", O_RDWR);
402 	if (fd < 0)
403 		return -1;
404 
405 	ret = ioctl(fd, TUNSETIFF, &ifr);
406 	if (ret)
407 		return -1;
408 
409 	return fd;
410 }
411 
412 static int tx_tap(int fd, void *pkt, size_t len)
413 {
414 	struct iovec iov[] = {
415 		{
416 			.iov_len = len,
417 			.iov_base = pkt,
418 		},
419 	};
420 	return writev(fd, iov, ARRAY_SIZE(iov));
421 }
422 
423 static int ifup(const char *ifname)
424 {
425 	struct ifreq ifr = {};
426 	int sk, ret;
427 
428 	strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
429 
430 	sk = socket(PF_INET, SOCK_DGRAM, 0);
431 	if (sk < 0)
432 		return -1;
433 
434 	ret = ioctl(sk, SIOCGIFFLAGS, &ifr);
435 	if (ret) {
436 		close(sk);
437 		return -1;
438 	}
439 
440 	ifr.ifr_flags |= IFF_UP;
441 	ret = ioctl(sk, SIOCSIFFLAGS, &ifr);
442 	if (ret) {
443 		close(sk);
444 		return -1;
445 	}
446 
447 	close(sk);
448 	return 0;
449 }
450 
451 static int init_prog_array(struct bpf_object *obj, struct bpf_map *prog_array)
452 {
453 	int i, err, map_fd, prog_fd;
454 	struct bpf_program *prog;
455 	char prog_name[32];
456 
457 	map_fd = bpf_map__fd(prog_array);
458 	if (map_fd < 0)
459 		return -1;
460 
461 	for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
462 		snprintf(prog_name, sizeof(prog_name), "flow_dissector_%d", i);
463 
464 		prog = bpf_object__find_program_by_name(obj, prog_name);
465 		if (!prog)
466 			return -1;
467 
468 		prog_fd = bpf_program__fd(prog);
469 		if (prog_fd < 0)
470 			return -1;
471 
472 		err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
473 		if (err)
474 			return -1;
475 	}
476 	return 0;
477 }
478 
479 static void run_tests_skb_less(int tap_fd, struct bpf_map *keys)
480 {
481 	int i, err, keys_fd;
482 
483 	keys_fd = bpf_map__fd(keys);
484 	if (CHECK(keys_fd < 0, "bpf_map__fd", "err %d\n", keys_fd))
485 		return;
486 
487 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
488 		/* Keep in sync with 'flags' from eth_get_headlen. */
489 		__u32 eth_get_headlen_flags =
490 			BPF_FLOW_DISSECTOR_F_PARSE_1ST_FRAG;
491 		LIBBPF_OPTS(bpf_test_run_opts, topts);
492 		struct bpf_flow_keys flow_keys = {};
493 		__u32 key = (__u32)(tests[i].keys.sport) << 16 |
494 			    tests[i].keys.dport;
495 
496 		/* For skb-less case we can't pass input flags; run
497 		 * only the tests that have a matching set of flags.
498 		 */
499 
500 		if (tests[i].flags != eth_get_headlen_flags)
501 			continue;
502 
503 		err = tx_tap(tap_fd, &tests[i].pkt, sizeof(tests[i].pkt));
504 		CHECK(err < 0, "tx_tap", "err %d errno %d\n", err, errno);
505 
506 		err = bpf_map_lookup_elem(keys_fd, &key, &flow_keys);
507 		ASSERT_OK(err, "bpf_map_lookup_elem");
508 
509 		CHECK_FLOW_KEYS(tests[i].name, flow_keys, tests[i].keys);
510 
511 		err = bpf_map_delete_elem(keys_fd, &key);
512 		ASSERT_OK(err, "bpf_map_delete_elem");
513 	}
514 }
515 
516 static void test_skb_less_prog_attach(struct bpf_flow *skel, int tap_fd)
517 {
518 	int err, prog_fd;
519 
520 	prog_fd = bpf_program__fd(skel->progs._dissect);
521 	if (CHECK(prog_fd < 0, "bpf_program__fd", "err %d\n", prog_fd))
522 		return;
523 
524 	err = bpf_prog_attach(prog_fd, 0, BPF_FLOW_DISSECTOR, 0);
525 	if (CHECK(err, "bpf_prog_attach", "err %d errno %d\n", err, errno))
526 		return;
527 
528 	run_tests_skb_less(tap_fd, skel->maps.last_dissection);
529 
530 	err = bpf_prog_detach2(prog_fd, 0, BPF_FLOW_DISSECTOR);
531 	CHECK(err, "bpf_prog_detach2", "err %d errno %d\n", err, errno);
532 }
533 
534 static void test_skb_less_link_create(struct bpf_flow *skel, int tap_fd)
535 {
536 	struct bpf_link *link;
537 	int err, net_fd;
538 
539 	net_fd = open("/proc/self/ns/net", O_RDONLY);
540 	if (CHECK(net_fd < 0, "open(/proc/self/ns/net)", "err %d\n", errno))
541 		return;
542 
543 	link = bpf_program__attach_netns(skel->progs._dissect, net_fd);
544 	if (!ASSERT_OK_PTR(link, "attach_netns"))
545 		goto out_close;
546 
547 	run_tests_skb_less(tap_fd, skel->maps.last_dissection);
548 
549 	err = bpf_link__destroy(link);
550 	CHECK(err, "bpf_link__destroy", "err %d\n", err);
551 out_close:
552 	close(net_fd);
553 }
554 
555 void test_flow_dissector(void)
556 {
557 	int i, err, prog_fd, keys_fd = -1, tap_fd;
558 	struct bpf_flow *skel;
559 
560 	skel = bpf_flow__open_and_load();
561 	if (CHECK(!skel, "skel", "failed to open/load skeleton\n"))
562 		return;
563 
564 	prog_fd = bpf_program__fd(skel->progs._dissect);
565 	if (CHECK(prog_fd < 0, "bpf_program__fd", "err %d\n", prog_fd))
566 		goto out_destroy_skel;
567 	keys_fd = bpf_map__fd(skel->maps.last_dissection);
568 	if (CHECK(keys_fd < 0, "bpf_map__fd", "err %d\n", keys_fd))
569 		goto out_destroy_skel;
570 	err = init_prog_array(skel->obj, skel->maps.jmp_table);
571 	if (CHECK(err, "init_prog_array", "err %d\n", err))
572 		goto out_destroy_skel;
573 
574 	for (i = 0; i < ARRAY_SIZE(tests); i++) {
575 		struct bpf_flow_keys flow_keys;
576 		LIBBPF_OPTS(bpf_test_run_opts, topts,
577 			.data_in = &tests[i].pkt,
578 			.data_size_in = sizeof(tests[i].pkt),
579 			.data_out = &flow_keys,
580 		);
581 		static struct bpf_flow_keys ctx = {};
582 
583 		if (tests[i].flags) {
584 			topts.ctx_in = &ctx;
585 			topts.ctx_size_in = sizeof(ctx);
586 			ctx.flags = tests[i].flags;
587 		}
588 
589 		err = bpf_prog_test_run_opts(prog_fd, &topts);
590 		ASSERT_OK(err, "test_run");
591 		ASSERT_EQ(topts.retval, 1, "test_run retval");
592 		ASSERT_EQ(topts.data_size_out, sizeof(flow_keys),
593 			  "test_run data_size_out");
594 		CHECK_FLOW_KEYS(tests[i].name, flow_keys, tests[i].keys);
595 	}
596 
597 	/* Do the same tests but for skb-less flow dissector.
598 	 * We use a known path in the net/tun driver that calls
599 	 * eth_get_headlen and we manually export bpf_flow_keys
600 	 * via BPF map in this case.
601 	 */
602 
603 	tap_fd = create_tap("tap0");
604 	CHECK(tap_fd < 0, "create_tap", "tap_fd %d errno %d\n", tap_fd, errno);
605 	err = ifup("tap0");
606 	CHECK(err, "ifup", "err %d errno %d\n", err, errno);
607 
608 	/* Test direct prog attachment */
609 	test_skb_less_prog_attach(skel, tap_fd);
610 	/* Test indirect prog attachment via link */
611 	test_skb_less_link_create(skel, tap_fd);
612 
613 	close(tap_fd);
614 out_destroy_skel:
615 	bpf_flow__destroy(skel);
616 }
617