// SPDX-License-Identifier: GPL-2.0 /** * Test XDP bonding support * * Sets up two bonded veth pairs between two fresh namespaces * and verifies that XDP_TX program loaded on a bond device * are correctly loaded onto the slave devices and XDP_TX'd * packets are balanced using bonding. */ #define _GNU_SOURCE #include #include #include #include "test_progs.h" #include "network_helpers.h" #include #include #include #include #include "xdp_dummy.skel.h" #include "xdp_redirect_multi_kern.skel.h" #include "xdp_tx.skel.h" #define BOND1_MAC {0x00, 0x11, 0x22, 0x33, 0x44, 0x55} #define BOND1_MAC_STR "00:11:22:33:44:55" #define BOND2_MAC {0x00, 0x22, 0x33, 0x44, 0x55, 0x66} #define BOND2_MAC_STR "00:22:33:44:55:66" #define NPACKETS 100 static int root_netns_fd = -1; static void restore_root_netns(void) { ASSERT_OK(setns(root_netns_fd, CLONE_NEWNET), "restore_root_netns"); } static int setns_by_name(char *name) { int nsfd, err; char nspath[PATH_MAX]; snprintf(nspath, sizeof(nspath), "%s/%s", "/var/run/netns", name); nsfd = open(nspath, O_RDONLY | O_CLOEXEC); if (nsfd < 0) return -1; err = setns(nsfd, CLONE_NEWNET); close(nsfd); return err; } static int get_rx_packets(const char *iface) { FILE *f; char line[512]; int iface_len = strlen(iface); f = fopen("/proc/net/dev", "r"); if (!f) return -1; while (fgets(line, sizeof(line), f)) { char *p = line; while (*p == ' ') p++; /* skip whitespace */ if (!strncmp(p, iface, iface_len)) { p += iface_len; if (*p++ != ':') continue; while (*p == ' ') p++; /* skip whitespace */ while (*p && *p != ' ') p++; /* skip rx bytes */ while (*p == ' ') p++; /* skip whitespace */ fclose(f); return atoi(p); } } fclose(f); return -1; } #define MAX_BPF_LINKS 8 struct skeletons { struct xdp_dummy *xdp_dummy; struct xdp_tx *xdp_tx; struct xdp_redirect_multi_kern *xdp_redirect_multi_kern; int nlinks; struct bpf_link *links[MAX_BPF_LINKS]; }; static int xdp_attach(struct skeletons *skeletons, struct bpf_program *prog, char *iface) { struct bpf_link *link; int ifindex; ifindex = if_nametoindex(iface); if (!ASSERT_GT(ifindex, 0, "get ifindex")) return -1; if (!ASSERT_LE(skeletons->nlinks+1, MAX_BPF_LINKS, "too many XDP programs attached")) return -1; link = bpf_program__attach_xdp(prog, ifindex); if (!ASSERT_OK_PTR(link, "attach xdp program")) return -1; skeletons->links[skeletons->nlinks++] = link; return 0; } enum { BOND_ONE_NO_ATTACH = 0, BOND_BOTH_AND_ATTACH, }; static const char * const mode_names[] = { [BOND_MODE_ROUNDROBIN] = "balance-rr", [BOND_MODE_ACTIVEBACKUP] = "active-backup", [BOND_MODE_XOR] = "balance-xor", [BOND_MODE_BROADCAST] = "broadcast", [BOND_MODE_8023AD] = "802.3ad", [BOND_MODE_TLB] = "balance-tlb", [BOND_MODE_ALB] = "balance-alb", }; static const char * const xmit_policy_names[] = { [BOND_XMIT_POLICY_LAYER2] = "layer2", [BOND_XMIT_POLICY_LAYER34] = "layer3+4", [BOND_XMIT_POLICY_LAYER23] = "layer2+3", [BOND_XMIT_POLICY_ENCAP23] = "encap2+3", [BOND_XMIT_POLICY_ENCAP34] = "encap3+4", }; static int bonding_setup(struct skeletons *skeletons, int mode, int xmit_policy, int bond_both_attach) { SYS(fail, "ip netns add ns_dst"); SYS(fail, "ip link add veth1_1 type veth peer name veth2_1 netns ns_dst"); SYS(fail, "ip link add veth1_2 type veth peer name veth2_2 netns ns_dst"); SYS(fail, "ip link add bond1 type bond mode %s xmit_hash_policy %s", mode_names[mode], xmit_policy_names[xmit_policy]); SYS(fail, "ip link set bond1 up address " BOND1_MAC_STR " addrgenmode none"); SYS(fail, "ip -netns ns_dst link add bond2 type bond mode %s xmit_hash_policy %s", mode_names[mode], xmit_policy_names[xmit_policy]); SYS(fail, "ip -netns ns_dst link set bond2 up address " BOND2_MAC_STR " addrgenmode none"); SYS(fail, "ip link set veth1_1 master bond1"); if (bond_both_attach == BOND_BOTH_AND_ATTACH) { SYS(fail, "ip link set veth1_2 master bond1"); } else { SYS(fail, "ip link set veth1_2 up addrgenmode none"); if (xdp_attach(skeletons, skeletons->xdp_dummy->progs.xdp_dummy_prog, "veth1_2")) return -1; } SYS(fail, "ip -netns ns_dst link set veth2_1 master bond2"); if (bond_both_attach == BOND_BOTH_AND_ATTACH) SYS(fail, "ip -netns ns_dst link set veth2_2 master bond2"); else SYS(fail, "ip -netns ns_dst link set veth2_2 up addrgenmode none"); /* Load a dummy program on sending side as with veth peer needs to have a * XDP program loaded as well. */ if (xdp_attach(skeletons, skeletons->xdp_dummy->progs.xdp_dummy_prog, "bond1")) return -1; if (bond_both_attach == BOND_BOTH_AND_ATTACH) { if (!ASSERT_OK(setns_by_name("ns_dst"), "set netns to ns_dst")) return -1; if (xdp_attach(skeletons, skeletons->xdp_tx->progs.xdp_tx, "bond2")) return -1; restore_root_netns(); } return 0; fail: return -1; } static void link_cleanup(struct skeletons *skeletons) { while (skeletons->nlinks) { skeletons->nlinks--; bpf_link__destroy(skeletons->links[skeletons->nlinks]); } } static void bonding_cleanup(struct skeletons *skeletons) { restore_root_netns(); link_cleanup(skeletons); ASSERT_OK(system("ip link delete bond1"), "delete bond1"); ASSERT_OK(system("ip link delete veth1_1"), "delete veth1_1"); ASSERT_OK(system("ip link delete veth1_2"), "delete veth1_2"); ASSERT_OK(system("ip netns delete ns_dst"), "delete ns_dst"); } static int send_udp_packets(int vary_dst_ip) { struct ethhdr eh = { .h_source = BOND1_MAC, .h_dest = BOND2_MAC, .h_proto = htons(ETH_P_IP), }; struct iphdr iph = {}; struct udphdr uh = {}; uint8_t buf[128]; int i, s = -1; int ifindex; s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); if (!ASSERT_GE(s, 0, "socket")) goto err; ifindex = if_nametoindex("bond1"); if (!ASSERT_GT(ifindex, 0, "get bond1 ifindex")) goto err; iph.ihl = 5; iph.version = 4; iph.tos = 16; iph.id = 1; iph.ttl = 64; iph.protocol = IPPROTO_UDP; iph.saddr = 1; iph.daddr = 2; iph.tot_len = htons(sizeof(buf) - ETH_HLEN); iph.check = 0; for (i = 1; i <= NPACKETS; i++) { int n; struct sockaddr_ll saddr_ll = { .sll_ifindex = ifindex, .sll_halen = ETH_ALEN, .sll_addr = BOND2_MAC, }; /* vary the UDP destination port for even distribution with roundrobin/xor modes */ uh.dest++; if (vary_dst_ip) iph.daddr++; /* construct a packet */ memcpy(buf, &eh, sizeof(eh)); memcpy(buf + sizeof(eh), &iph, sizeof(iph)); memcpy(buf + sizeof(eh) + sizeof(iph), &uh, sizeof(uh)); n = sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&saddr_ll, sizeof(saddr_ll)); if (!ASSERT_EQ(n, sizeof(buf), "sendto")) goto err; } return 0; err: if (s >= 0) close(s); return -1; } static void test_xdp_bonding_with_mode(struct skeletons *skeletons, int mode, int xmit_policy) { int bond1_rx; if (bonding_setup(skeletons, mode, xmit_policy, BOND_BOTH_AND_ATTACH)) goto out; if (send_udp_packets(xmit_policy != BOND_XMIT_POLICY_LAYER34)) goto out; bond1_rx = get_rx_packets("bond1"); ASSERT_EQ(bond1_rx, NPACKETS, "expected more received packets"); switch (mode) { case BOND_MODE_ROUNDROBIN: case BOND_MODE_XOR: { int veth1_rx = get_rx_packets("veth1_1"); int veth2_rx = get_rx_packets("veth1_2"); int diff = abs(veth1_rx - veth2_rx); ASSERT_GE(veth1_rx + veth2_rx, NPACKETS, "expected more packets"); switch (xmit_policy) { case BOND_XMIT_POLICY_LAYER2: ASSERT_GE(diff, NPACKETS, "expected packets on only one of the interfaces"); break; case BOND_XMIT_POLICY_LAYER23: case BOND_XMIT_POLICY_LAYER34: ASSERT_LT(diff, NPACKETS/2, "expected even distribution of packets"); break; default: PRINT_FAIL("Unimplemented xmit_policy=%d\n", xmit_policy); break; } break; } case BOND_MODE_ACTIVEBACKUP: { int veth1_rx = get_rx_packets("veth1_1"); int veth2_rx = get_rx_packets("veth1_2"); int diff = abs(veth1_rx - veth2_rx); ASSERT_GE(diff, NPACKETS, "expected packets on only one of the interfaces"); break; } default: PRINT_FAIL("Unimplemented xmit_policy=%d\n", xmit_policy); break; } out: bonding_cleanup(skeletons); } /* Test the broadcast redirection using xdp_redirect_map_multi_prog and adding * all the interfaces to it and checking that broadcasting won't send the packet * to neither the ingress bond device (bond2) or its slave (veth2_1). */ static void test_xdp_bonding_redirect_multi(struct skeletons *skeletons) { static const char * const ifaces[] = {"bond2", "veth2_1", "veth2_2"}; int veth1_1_rx, veth1_2_rx; int err; if (bonding_setup(skeletons, BOND_MODE_ROUNDROBIN, BOND_XMIT_POLICY_LAYER23, BOND_ONE_NO_ATTACH)) goto out; if (!ASSERT_OK(setns_by_name("ns_dst"), "could not set netns to ns_dst")) goto out; /* populate the devmap with the relevant interfaces */ for (int i = 0; i < ARRAY_SIZE(ifaces); i++) { int ifindex = if_nametoindex(ifaces[i]); int map_fd = bpf_map__fd(skeletons->xdp_redirect_multi_kern->maps.map_all); if (!ASSERT_GT(ifindex, 0, "could not get interface index")) goto out; err = bpf_map_update_elem(map_fd, &ifindex, &ifindex, 0); if (!ASSERT_OK(err, "add interface to map_all")) goto out; } if (xdp_attach(skeletons, skeletons->xdp_redirect_multi_kern->progs.xdp_redirect_map_multi_prog, "bond2")) goto out; restore_root_netns(); if (send_udp_packets(BOND_MODE_ROUNDROBIN)) goto out; veth1_1_rx = get_rx_packets("veth1_1"); veth1_2_rx = get_rx_packets("veth1_2"); ASSERT_EQ(veth1_1_rx, 0, "expected no packets on veth1_1"); ASSERT_GE(veth1_2_rx, NPACKETS, "expected packets on veth1_2"); out: restore_root_netns(); bonding_cleanup(skeletons); } /* Test that XDP programs cannot be attached to both the bond master and slaves simultaneously */ static void test_xdp_bonding_attach(struct skeletons *skeletons) { struct bpf_link *link = NULL; struct bpf_link *link2 = NULL; int veth, bond, err; if (!ASSERT_OK(system("ip link add veth type veth"), "add veth")) goto out; if (!ASSERT_OK(system("ip link add bond type bond"), "add bond")) goto out; veth = if_nametoindex("veth"); if (!ASSERT_GE(veth, 0, "if_nametoindex veth")) goto out; bond = if_nametoindex("bond"); if (!ASSERT_GE(bond, 0, "if_nametoindex bond")) goto out; /* enslaving with a XDP program loaded is allowed */ link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth); if (!ASSERT_OK_PTR(link, "attach program to veth")) goto out; err = system("ip link set veth master bond"); if (!ASSERT_OK(err, "set veth master")) goto out; bpf_link__destroy(link); link = NULL; /* attaching to slave when master has no program is allowed */ link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth); if (!ASSERT_OK_PTR(link, "attach program to slave when enslaved")) goto out; /* attaching to master not allowed when slave has program loaded */ link2 = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond); if (!ASSERT_ERR_PTR(link2, "attach program to master when slave has program")) goto out; bpf_link__destroy(link); link = NULL; /* attaching XDP program to master allowed when slave has no program */ link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond); if (!ASSERT_OK_PTR(link, "attach program to master")) goto out; /* attaching to slave not allowed when master has program loaded */ link2 = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth); if (!ASSERT_ERR_PTR(link2, "attach program to slave when master has program")) goto out; bpf_link__destroy(link); link = NULL; /* test program unwinding with a non-XDP slave */ if (!ASSERT_OK(system("ip link add vxlan type vxlan id 1 remote 1.2.3.4 dstport 0 dev lo"), "add vxlan")) goto out; err = system("ip link set vxlan master bond"); if (!ASSERT_OK(err, "set vxlan master")) goto out; /* attaching not allowed when one slave does not support XDP */ link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond); if (!ASSERT_ERR_PTR(link, "attach program to master when slave does not support XDP")) goto out; out: bpf_link__destroy(link); bpf_link__destroy(link2); system("ip link del veth"); system("ip link del bond"); system("ip link del vxlan"); } /* Test with nested bonding devices to catch issue with negative jump label count */ static void test_xdp_bonding_nested(struct skeletons *skeletons) { struct bpf_link *link = NULL; int bond, err; if (!ASSERT_OK(system("ip link add bond type bond"), "add bond")) goto out; bond = if_nametoindex("bond"); if (!ASSERT_GE(bond, 0, "if_nametoindex bond")) goto out; if (!ASSERT_OK(system("ip link add bond_nest1 type bond"), "add bond_nest1")) goto out; err = system("ip link set bond_nest1 master bond"); if (!ASSERT_OK(err, "set bond_nest1 master")) goto out; if (!ASSERT_OK(system("ip link add bond_nest2 type bond"), "add bond_nest1")) goto out; err = system("ip link set bond_nest2 master bond_nest1"); if (!ASSERT_OK(err, "set bond_nest2 master")) goto out; link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, bond); ASSERT_OK_PTR(link, "attach program to master"); out: bpf_link__destroy(link); system("ip link del bond"); system("ip link del bond_nest1"); system("ip link del bond_nest2"); } /* * Test that XDP redirect via xdp_master_redirect() does not crash when * the bond master device is not up. When bond is in round-robin mode but * never opened, rr_tx_counter is NULL. */ static void test_xdp_bonding_redirect_no_up(struct skeletons *skeletons) { struct nstoken *nstoken = NULL; int xdp_pass_fd; int veth1_ifindex; int err; char pkt[ETH_HLEN + 1]; struct xdp_md ctx_in = {}; DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = &pkt, .data_size_in = sizeof(pkt), .ctx_in = &ctx_in, .ctx_size_in = sizeof(ctx_in), .flags = BPF_F_TEST_XDP_LIVE_FRAMES, .repeat = 1, .batch_size = 1, ); /* We can't use bonding_setup() because bond will be active */ SYS(out, "ip netns add ns_rr_no_up"); nstoken = open_netns("ns_rr_no_up"); if (!ASSERT_OK_PTR(nstoken, "open ns_rr_no_up")) goto out; /* bond0: active-backup, UP with slave veth0. * Attaching native XDP to bond0 enables bpf_master_redirect_enabled_key * globally. */ SYS(out, "ip link add bond0 type bond mode active-backup"); SYS(out, "ip link add veth0 type veth peer name veth0p"); SYS(out, "ip link set veth0 master bond0"); SYS(out, "ip link set bond0 up"); SYS(out, "ip link set veth0p up"); /* bond1: round-robin, never UP -> rr_tx_counter stays NULL */ SYS(out, "ip link add bond1 type bond mode balance-rr"); SYS(out, "ip link add veth1 type veth peer name veth1p"); SYS(out, "ip link set veth1 master bond1"); veth1_ifindex = if_nametoindex("veth1"); if (!ASSERT_GT(veth1_ifindex, 0, "veth1_ifindex")) goto out; /* Attach native XDP to bond0 -> enables global redirect key */ if (xdp_attach(skeletons, skeletons->xdp_tx->progs.xdp_tx, "bond0")) goto out; /* Attach generic XDP (XDP_TX) to veth1. * When packets arrive at veth1 via netif_receive_skb, do_xdp_generic() * runs this program. XDP_TX + bond slave triggers xdp_master_redirect(). */ err = bpf_xdp_attach(veth1_ifindex, bpf_program__fd(skeletons->xdp_tx->progs.xdp_tx), XDP_FLAGS_SKB_MODE, NULL); if (!ASSERT_OK(err, "attach generic XDP to veth1")) goto out; /* Run BPF_PROG_TEST_RUN with XDP_PASS live frames on veth1. * XDP_PASS frames become SKBs with skb->dev = veth1, entering * netif_receive_skb -> do_xdp_generic -> xdp_master_redirect. * Without the fix, bond_rr_gen_slave_id() dereferences NULL * rr_tx_counter and crashes. */ xdp_pass_fd = bpf_program__fd(skeletons->xdp_dummy->progs.xdp_dummy_prog); memset(pkt, 0, sizeof(pkt)); ctx_in.data_end = sizeof(pkt); ctx_in.ingress_ifindex = veth1_ifindex; err = bpf_prog_test_run_opts(xdp_pass_fd, &opts); ASSERT_OK(err, "xdp_pass test_run should not crash"); out: link_cleanup(skeletons); close_netns(nstoken); SYS_NOFAIL("ip netns del ns_rr_no_up"); } static void test_xdp_bonding_features(struct skeletons *skeletons) { LIBBPF_OPTS(bpf_xdp_query_opts, query_opts); int bond_idx, veth1_idx, err; struct bpf_link *link = NULL; if (!ASSERT_OK(system("ip link add bond type bond"), "add bond")) goto out; bond_idx = if_nametoindex("bond"); if (!ASSERT_GE(bond_idx, 0, "if_nametoindex bond")) goto out; /* query default xdp-feature for bond device */ err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; if (!ASSERT_EQ(query_opts.feature_flags, 0, "bond query_opts.feature_flags")) goto out; if (!ASSERT_OK(system("ip link add veth0 type veth peer name veth1"), "add veth{0,1} pair")) goto out; if (!ASSERT_OK(system("ip link add veth2 type veth peer name veth3"), "add veth{2,3} pair")) goto out; if (!ASSERT_OK(system("ip link set veth0 master bond"), "add veth0 to master bond")) goto out; /* xdp-feature for bond device should be obtained from the single slave * device (veth0) */ err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; if (!ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_RX_SG, "bond query_opts.feature_flags")) goto out; veth1_idx = if_nametoindex("veth1"); if (!ASSERT_GE(veth1_idx, 0, "if_nametoindex veth1")) goto out; link = bpf_program__attach_xdp(skeletons->xdp_dummy->progs.xdp_dummy_prog, veth1_idx); if (!ASSERT_OK_PTR(link, "attach program to veth1")) goto out; /* xdp-feature for veth0 are changed */ err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; if (!ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_RX_SG | NETDEV_XDP_ACT_NDO_XMIT | NETDEV_XDP_ACT_NDO_XMIT_SG, "bond query_opts.feature_flags")) goto out; if (!ASSERT_OK(system("ip link set veth2 master bond"), "add veth2 to master bond")) goto out; err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; /* xdp-feature for bond device should be set to the most restrict * value obtained from attached slave devices (veth0 and veth2) */ if (!ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_RX_SG, "bond query_opts.feature_flags")) goto out; if (!ASSERT_OK(system("ip link set veth2 nomaster"), "del veth2 to master bond")) goto out; err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; if (!ASSERT_EQ(query_opts.feature_flags, NETDEV_XDP_ACT_BASIC | NETDEV_XDP_ACT_REDIRECT | NETDEV_XDP_ACT_RX_SG | NETDEV_XDP_ACT_NDO_XMIT | NETDEV_XDP_ACT_NDO_XMIT_SG, "bond query_opts.feature_flags")) goto out; if (!ASSERT_OK(system("ip link set veth0 nomaster"), "del veth0 to master bond")) goto out; err = bpf_xdp_query(bond_idx, XDP_FLAGS_DRV_MODE, &query_opts); if (!ASSERT_OK(err, "bond bpf_xdp_query")) goto out; ASSERT_EQ(query_opts.feature_flags, 0, "bond query_opts.feature_flags"); out: bpf_link__destroy(link); system("ip link del veth0"); system("ip link del veth2"); system("ip link del bond"); } /* * Test that changing xmit_hash_policy to vlan+srcmac is rejected when a * native XDP program is loaded on a bond in 802.3ad or balance-xor mode. * These modes support XDP only when xmit_hash_policy != vlan+srcmac; freely * changing the policy creates an inconsistency that triggers a WARNING in * dev_xdp_uninstall() during device teardown. */ static void test_xdp_bonding_xmit_policy_compat(struct skeletons *skeletons) { struct nstoken *nstoken = NULL; int bond_ifindex = -1; int xdp_fd, err; SYS(out, "ip netns add ns_xmit_policy"); nstoken = open_netns("ns_xmit_policy"); if (!ASSERT_OK_PTR(nstoken, "open ns_xmit_policy")) goto out; /* 802.3ad with layer2+3 policy: native XDP is supported */ SYS(out, "ip link add bond0 type bond mode 802.3ad xmit_hash_policy layer2+3"); SYS(out, "ip link add veth0 type veth peer name veth0p"); SYS(out, "ip link set veth0 master bond0"); SYS(out, "ip link set bond0 up"); bond_ifindex = if_nametoindex("bond0"); if (!ASSERT_GT(bond_ifindex, 0, "bond0 ifindex")) goto out; xdp_fd = bpf_program__fd(skeletons->xdp_dummy->progs.xdp_dummy_prog); if (!ASSERT_GE(xdp_fd, 0, "xdp_dummy fd")) goto out; err = bpf_xdp_attach(bond_ifindex, xdp_fd, XDP_FLAGS_DRV_MODE, NULL); if (!ASSERT_OK(err, "attach XDP to bond0")) goto out; /* With XDP loaded, switching to vlan+srcmac must be rejected */ err = system("ip link set bond0 type bond xmit_hash_policy vlan+srcmac 2>/dev/null"); ASSERT_NEQ(err, 0, "vlan+srcmac change with XDP loaded should fail"); /* Detach XDP first, then the same change must succeed */ ASSERT_OK(bpf_xdp_detach(bond_ifindex, XDP_FLAGS_DRV_MODE, NULL), "detach XDP from bond0"); bond_ifindex = -1; err = system("ip link set bond0 type bond xmit_hash_policy vlan+srcmac 2>/dev/null"); ASSERT_OK(err, "vlan+srcmac change without XDP should succeed"); out: if (bond_ifindex > 0) bpf_xdp_detach(bond_ifindex, XDP_FLAGS_DRV_MODE, NULL); close_netns(nstoken); SYS_NOFAIL("ip netns del ns_xmit_policy"); } static int libbpf_debug_print(enum libbpf_print_level level, const char *format, va_list args) { if (level != LIBBPF_WARN) vprintf(format, args); return 0; } struct bond_test_case { char *name; int mode; int xmit_policy; }; static struct bond_test_case bond_test_cases[] = { { "xdp_bonding_roundrobin", BOND_MODE_ROUNDROBIN, BOND_XMIT_POLICY_LAYER23, }, { "xdp_bonding_activebackup", BOND_MODE_ACTIVEBACKUP, BOND_XMIT_POLICY_LAYER23 }, { "xdp_bonding_xor_layer2", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER2, }, { "xdp_bonding_xor_layer23", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER23, }, { "xdp_bonding_xor_layer34", BOND_MODE_XOR, BOND_XMIT_POLICY_LAYER34, }, }; void serial_test_xdp_bonding(void) { libbpf_print_fn_t old_print_fn; struct skeletons skeletons = {}; int i; old_print_fn = libbpf_set_print(libbpf_debug_print); root_netns_fd = open("/proc/self/ns/net", O_RDONLY); if (!ASSERT_GE(root_netns_fd, 0, "open /proc/self/ns/net")) goto out; skeletons.xdp_dummy = xdp_dummy__open_and_load(); if (!ASSERT_OK_PTR(skeletons.xdp_dummy, "xdp_dummy__open_and_load")) goto out; skeletons.xdp_tx = xdp_tx__open_and_load(); if (!ASSERT_OK_PTR(skeletons.xdp_tx, "xdp_tx__open_and_load")) goto out; skeletons.xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load(); if (!ASSERT_OK_PTR(skeletons.xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load")) goto out; if (test__start_subtest("xdp_bonding_attach")) test_xdp_bonding_attach(&skeletons); if (test__start_subtest("xdp_bonding_nested")) test_xdp_bonding_nested(&skeletons); if (test__start_subtest("xdp_bonding_features")) test_xdp_bonding_features(&skeletons); for (i = 0; i < ARRAY_SIZE(bond_test_cases); i++) { struct bond_test_case *test_case = &bond_test_cases[i]; if (test__start_subtest(test_case->name)) test_xdp_bonding_with_mode( &skeletons, test_case->mode, test_case->xmit_policy); } if (test__start_subtest("xdp_bonding_xmit_policy_compat")) test_xdp_bonding_xmit_policy_compat(&skeletons); if (test__start_subtest("xdp_bonding_redirect_multi")) test_xdp_bonding_redirect_multi(&skeletons); if (test__start_subtest("xdp_bonding_redirect_no_up")) test_xdp_bonding_redirect_no_up(&skeletons); out: xdp_dummy__destroy(skeletons.xdp_dummy); xdp_tx__destroy(skeletons.xdp_tx); xdp_redirect_multi_kern__destroy(skeletons.xdp_redirect_multi_kern); libbpf_set_print(old_print_fn); if (root_netns_fd >= 0) close(root_netns_fd); }