On Sat Jul 4, 2026 at 5:21 AM EDT, Avinash Duduskar wrote:
> Cover both new VLAN flags in the fib_lookup test. BPF_FIB_LOOKUP_VLAN
> reduces a VLAN egress to its physical parent plus the tag, and
> BPF_FIB_LOOKUP_VLAN_INPUT scopes the lookup to a VLAN subinterface.
>
> BPF_FIB_LOOKUP_VLAN is XDP-only, since VLAN devices have no XDP xmit; the
> tc helper rejects it with -EINVAL, which the table runner asserts for
> every flag arm, and the egress result is checked through
> bpf_xdp_fib_lookup(). Non-VLAN cases run through both helpers and assert
> the path-independent results match; the XDP loop also checks dmac and,
> for the tot_len cases, the route mtu_result, so the VLAN-egress dmac and
> frag-needed coverage stays even though the tc path no longer reaches it.
>
> The egress arms pin the reduction (parent ifindex plus tag, including
> via a neighbour on the VLAN device, in OUTPUT mode, over a bond, and
> through a DIRECT|TBID table) and the failure contract: a stacked-VLAN
> (QinQ) egress returns BPF_FIB_LKUP_RET_VLAN_FAILURE with params->ifindex
> left at the input. That is distinct from a no-neighbour return, which
> reports the egress ifindex; only VLAN_FAILURE rewinds params->ifindex,
> and a guard arm whose input and egress devices differ pins the
> distinction. The VLAN_FAILURE arms are IPv4; the IPv6 path reaches it
> through the same shared code, so an IPv6 arm would only re-test that.
>
> The input arms use an iif rule that routes one destination to two
> gateways, so the asserted gateway reveals which device the lookup used
> as ingress, including VRF table selection through the l3mdev rule and
> l3mdev_fib_table_rcu(). A cross-netns subtest moves a VLAN device into a
> second netns while it stays registered on its parent and checks both
> directions fail closed at the boundary.
>
> A live-frames subtest (test_fib_lookup_vlan_redirect, with
> BPF_F_TEST_XDP_LIVE_FRAMES) drives real frames through the native
> xdp_do_redirect() / xdp_do_flush() path: a reducible egress is
> redirected to the parent and delivered to its peer, while a QinQ egress
> is passed to the stack, since redirecting to the VLAN device would drop
> the frame at flush (no ndo_xdp_xmit).
>
> The remaining per-case assertions are in the test table: resolution
> semantics, the -EINVAL and NOT_FWDED error arms, and the SRC/SKIP_NEIGH
> combinations.
>
> Signed-off-by: Avinash Duduskar <[email protected]>

Reviewed-by: Emil Tsalapatis <[email protected]>

Nit below.

> ---
>  .../selftests/bpf/prog_tests/fib_lookup.c     | 712 +++++++++++++++++-
>  .../testing/selftests/bpf/progs/fib_lookup.c  |  36 +
>  2 files changed, 744 insertions(+), 4 deletions(-)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c 
> b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c
> index bd7658958004..18b6fdeca382 100644
> --- a/tools/testing/selftests/bpf/prog_tests/fib_lookup.c
> +++ b/tools/testing/selftests/bpf/prog_tests/fib_lookup.c

<SNIP>

> +#define VLAN_ID                      100
> +#define VLAN_IFACE           "veth1.100"
> +#define VLAN_ID_DOWN         102
> +#define VLAN_IFACE_DOWN              "veth1.102"
> +#define QINQ_OUTER_IFACE     "veth1.200"
> +#define QINQ_INNER_IFACE     "veth1.200.300"
> +#define VLAN_TABLE           "300"
> +#define IPV4_VLAN_IFACE_ADDR "10.5.0.254"
> +#define IPV4_VLAN_EGRESS_DST "10.5.0.2"
> +#define IPV4_QINQ_DST                "10.7.0.2"
> +#define IPV4_VLAN_DST                "10.6.0.2"
> +#define IPV4_VLAN_GW         "10.5.0.1"
> +#define IPV6_VLAN_IFACE_ADDR "fd02::254"
> +#define IPV6_VLAN_EGRESS_DST "fd02::2"
> +#define IPV6_VLAN_DST                "fd03::2"
> +#define IPV6_VLAN_GW         "fd02::1"
> +#define VLAN_VID_UNUSED              999
> +#define VRF_IFACE            "vrf-blue"
> +#define VRF_TABLE            "1000"
> +#define VRF_VLAN_ID          101
> +#define VRF_VLAN_IFACE               "veth1.101"
> +#define IPV4_VRF_IFACE_ADDR  "10.8.0.254"
> +#define IPV4_VRF_GW          "10.8.0.1"
> +#define IPV4_VRF_DST         "10.9.0.2"
> +#define TBID_VLAN_ID         50
> +#define TBID_VLAN_IFACE              "veth2.50"
> +#define IPV4_TBID_VLAN_DST   "172.2.0.2"
> +#define IPV4_BOND_VLAN_DST   "10.11.0.2"
> +#define IPV4_VLAN_MTU_DST    "10.5.9.2"
> +#define QINQ_AD_VLAN_ID              200
> +#define QINQ_INNER_VLAN_ID   300
> +#define BOND_IFACE           "bond99"
> +#define BOND_PORT            "veth3"
> +#define BOND_PORT_PEER               "veth4"
> +#define BOND_VLAN_ID         500
>  #define DMAC                 "11:11:11:11:11:11"
>  #define DMAC_INIT { 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, }
>  #define DMAC2                        "01:01:01:01:01:01"


Here we add a bunch of shorthands for the topology we're setting up,
but not for all interfaces/IPs. E.g.,

<SNIP>

> +/*
> + * A VLAN device can be moved to another netns while staying registered
> + * on its parent. Neither direction may then cross the boundary: the
> + * egress flag must not publish the foreign parent's ifindex, and the
> + * input flag must fail closed rather than use a foreign ingress.
> + */
> +void test_fib_lookup_vlan_netns(void)
> +{
> +     struct bpf_fib_lookup *fib_params;
> +     struct nstoken *nstoken = NULL;
> +     struct __sk_buff skb = { };
> +     struct fib_lookup *skel = NULL;
> +     int prog_fd, xdp_fd, err, parent_idx, vlan_idx;
> +
> +     LIBBPF_OPTS(bpf_test_run_opts, run_opts,
> +                 .data_in = &pkt_v6,
> +                 .data_size_in = sizeof(pkt_v6),
> +                 .ctx_in = &skb,
> +                 .ctx_size_in = sizeof(skb),
> +     );
> +     LIBBPF_OPTS(bpf_test_run_opts, xdp_opts,
> +                 .data_in = &pkt_v6,
> +                 .data_size_in = sizeof(pkt_v6),
> +     );
> +
> +     skel = fib_lookup__open_and_load();
> +     if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
> +             return;
> +     prog_fd = bpf_program__fd(skel->progs.fib_lookup);
> +     xdp_fd = bpf_program__fd(skel->progs.fib_lookup_xdp);
> +     fib_params = &skel->bss->fib_params;
> +
> +     SYS(fail, "ip netns add %s", NS_VLAN_A);
> +     SYS(fail, "ip netns add %s", NS_VLAN_B);
> +
> +     nstoken = open_netns(NS_VLAN_A);
> +     if (!ASSERT_OK_PTR(nstoken, "open_netns(a)"))
> +             goto fail;
> +
> +     SYS(fail, "ip link add veth7 type veth peer name veth8");
> +     SYS(fail, "ip link set dev veth7 up");
> +     SYS(fail, "ip link add link veth7 name veth7.66 type vlan id 66");
> +     SYS(fail, "ip link set veth7.66 netns %s", NS_VLAN_B);
> +
> +     parent_idx = if_nametoindex("veth7");
> +     if (!ASSERT_NEQ(parent_idx, 0, "if_nametoindex(veth7)"))
> +             goto fail;
> +
> +     /*
> +      * input: the moved device is still in veth7's VLAN group, but it
> +      * lives in another netns, so the lookup must fail closed
> +      */
> +     skb.ifindex = parent_idx;
> +     memset(fib_params, 0, sizeof(*fib_params));
> +     fib_params->family = AF_INET;
> +     fib_params->l4_protocol = IPPROTO_TCP;
> +     fib_params->ifindex = parent_idx;
> +     fib_params->h_vlan_proto = htons(ETH_P_8021Q);
> +     fib_params->h_vlan_TCI = htons(66);
> +     if (!ASSERT_EQ(inet_pton(AF_INET, "10.66.0.2", &fib_params->ipv4_dst),
> +                    1, "inet_pton(dst)"))
> +             goto fail;
> +

The IPs are hardcoded here (and in the rest of the code below). AFAICT the
logic is that the defines are to make it more obvious as to what the test
is about. Is there a logic of this kind behind the naming convention?

> +     skel->bss->fib_lookup_ret = -1;
> +     skel->bss->lookup_flags = BPF_FIB_LOOKUP_VLAN_INPUT |
> +                               BPF_FIB_LOOKUP_SKIP_NEIGH;
> +     err = bpf_prog_test_run_opts(prog_fd, &run_opts);
> +     if (!ASSERT_OK(err, "test_run(input)"))
> +             goto fail;
> +     ASSERT_EQ(skel->bss->fib_lookup_ret, BPF_FIB_LKUP_RET_NOT_FWDED,
> +               "input across netns fails closed");
> +     ASSERT_EQ(fib_params->ifindex, parent_idx, "ifindex untouched");
> +     ASSERT_EQ(fib_params->h_vlan_TCI, htons(66), "tag untouched");
> +
> +     close_netns(nstoken);
> +     nstoken = open_netns(NS_VLAN_B);
> +     if (!ASSERT_OK_PTR(nstoken, "open_netns(b)"))
> +             goto fail;
> +
> +     /*
> +      * egress: the fib result is the VLAN device here, but its parent
> +      * is in the other netns, so the swap must not happen
> +      */
> +     SYS(fail, "ip link set dev veth7.66 up");
> +     SYS(fail, "ip addr add 10.66.0.1/24 dev veth7.66");
> +     err = write_sysctl("/proc/sys/net/ipv4/conf/veth7.66/forwarding", "1");
> +     if (!ASSERT_OK(err, "write_sysctl(forwarding)"))
> +             goto fail;
> +
> +     vlan_idx = if_nametoindex("veth7.66");
> +     if (!ASSERT_NEQ(vlan_idx, 0, "if_nametoindex(veth7.66)"))
> +             goto fail;
> +
> +     memset(fib_params, 0, sizeof(*fib_params));
> +     fib_params->family = AF_INET;
> +     fib_params->l4_protocol = IPPROTO_TCP;
> +     fib_params->ifindex = vlan_idx;
> +     if (!ASSERT_EQ(inet_pton(AF_INET, "10.66.0.2", &fib_params->ipv4_dst),
> +                    1, "inet_pton(dst)") ||
> +         !ASSERT_EQ(inet_pton(AF_INET, "10.66.0.1", &fib_params->ipv4_src),
> +                    1, "inet_pton(src)"))
> +             goto fail;

<SNIP>

Reply via email to