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