]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: bridge: vlan: fix vlan range dumps starting with pvid
authorNikolay Aleksandrov <razor@blackwall.org>
Tue, 21 Jul 2026 14:09:21 +0000 (17:09 +0300)
committerJakub Kicinski <kuba@kernel.org>
Wed, 22 Jul 2026 17:23:18 +0000 (10:23 -0700)
There is a bug in all range dumps that rely on br_vlan_can_enter_range()
when the PVID is a range starting VLAN, all following VLANs that match
its flags can enter the range, but when the range is filled in only the
PVID VLAN is dumped and the rest of the range is discarded because
br_vlan_fill_vids() checks for the PVID flag. Since the PVID VLAN can
be only one, we need to break ranges around it, the best way to do that
consistently for all is to alter br_vlan_can_enter_range() to take into
account the PVID and return false to break the range when it's matched.

Before the fix:
$ ip l add br0 type bridge vlan_filtering 1
$ ip l add dumdum type dummy
$ ip l set dumdum master br0
$ ip l set br0 up
$ ip l set dumdum up
$ bridge vlan add dev dumdum vid 1 pvid untagged master
$ bridge vlan add dev dumdum vid 2 untagged master
$ bridge vlan show dev dumdum # use legacy dump to show all vlans
port              vlan-id
dumdum            1 PVID Egress Untagged
                  2 Egress Untagged

$ bridge -d vlan show dev dumdum # use the new dump (RTM_GETVLAN)
port              vlan-id
dumdum            1 PVID Egress Untagged
                    state forwarding mcast_router 1

VLAN 2 is missing, and if there are more matching VLANs afterwards
they'd be missing too.

After the fix:
[ same setup steps ]
$ bridge vlan show dev dumdum
port              vlan-id
dumdum            1 PVID Egress Untagged
                  2 Egress Untagged
$ bridge -d vlan show dev dumdum # use the new dump (RTM_GETVLAN)
port              vlan-id
dumdum            1 PVID Egress Untagged
                    state forwarding mcast_router 1
                  2 Egress Untagged
                    state forwarding mcast_router 1

Fixes: 0ab558795184 ("net: bridge: vlan: add rtm range support")
Signed-off-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20260721140922.682265-2-razor@blackwall.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/bridge/br_netlink_tunnel.c
net/bridge/br_private.h
net/bridge/br_vlan.c
net/bridge/br_vlan_options.c

index 71a12da30004c795736aadf5d3d535b439aa5ce7..a713668ea34f0ec655ef056fc0c411a4852c0b5c 100644 (file)
@@ -271,7 +271,8 @@ static void __vlan_tunnel_handle_range(const struct net_bridge_port *p,
        if (!*v_start)
                goto out_init;
 
-       if (v && curr_change && br_vlan_can_enter_range(v, *v_end)) {
+       if (v && curr_change &&
+           br_vlan_can_enter_range(v, *v_end, br_get_pvid(vg))) {
                *v_end = v;
                return;
        }
index d55ea9516e3e369bb9e0045fca5b8d034ef5a45f..d3880f31edc4e1f6d5eb4b335945c096cc3b5c24 100644 (file)
@@ -1627,7 +1627,8 @@ void br_vlan_notify(const struct net_bridge *br,
                    u16 vid, u16 vid_range,
                    int cmd);
 bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
-                            const struct net_bridge_vlan *range_end);
+                            const struct net_bridge_vlan *range_end,
+                            u16 pvid);
 
 void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
                                    struct net_device_path_ctx *ctx,
@@ -1874,7 +1875,8 @@ static inline void br_vlan_notify(const struct net_bridge *br,
 }
 
 static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
-                                          const struct net_bridge_vlan *range_end)
+                                          const struct net_bridge_vlan *range_end,
+                                          u16 pvid)
 {
        return true;
 }
index 5560afcaaca3273510d756a53399584baf0c64d4..31c1b2cf75d9231bc895aea0e91ea2d6494841e7 100644 (file)
@@ -1982,9 +1982,11 @@ out_kfree:
 
 /* check if v_curr can enter a range ending in range_end */
 bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
-                            const struct net_bridge_vlan *range_end)
+                            const struct net_bridge_vlan *range_end,
+                            u16 pvid)
 {
-       return v_curr->vid - range_end->vid == 1 &&
+       return v_curr->vid != pvid && range_end->vid != pvid &&
+              v_curr->vid - range_end->vid == 1 &&
               range_end->flags == v_curr->flags &&
               br_vlan_opts_eq_range(v_curr, range_end);
 }
@@ -2066,8 +2068,8 @@ static int br_vlan_dump_dev(const struct net_device *dev,
                        idx += range_end->vid - range_start->vid + 1;
 
                        range_start = v;
-               } else if (dump_stats || v->vid == pvid ||
-                          !br_vlan_can_enter_range(v, range_end)) {
+               } else if (dump_stats ||
+                          !br_vlan_can_enter_range(v, range_end, pvid)) {
                        u16 vlan_flags = br_vlan_flags(range_start, pvid);
 
                        if (!br_vlan_fill_vids(skb, range_start->vid,
index fcc200c3e3da260465f9eb8ab1c8c87e2d3f7588..cb0f556ff40dbe85a15cf7f49c54a21ee5595d06 100644 (file)
@@ -350,8 +350,7 @@ int br_vlan_process_options(const struct net_bridge *br,
                                continue;
                        }
 
-                       if (v->vid == pvid ||
-                           !br_vlan_can_enter_range(v, curr_end)) {
+                       if (!br_vlan_can_enter_range(v, curr_end, pvid)) {
                                br_vlan_notify(br, p, curr_start->vid,
                                               curr_end->vid, RTM_NEWVLAN);
                                curr_start = v;