]> git.dujemihanovic.xyz Git - linux.git/commitdiff
net: bridge: add vlan mcast snooping knob
authorNikolay Aleksandrov <nikolay@nvidia.com>
Mon, 19 Jul 2021 17:06:28 +0000 (20:06 +0300)
committerDavid S. Miller <davem@davemloft.net>
Tue, 20 Jul 2021 12:41:20 +0000 (05:41 -0700)
Add a global knob that controls if vlan multicast snooping is enabled.
The proper contexts (vlan or bridge-wide) will be chosen based on the knob
when processing packets and changing bridge device state. Note that
vlans have their individual mcast snooping enabled by default, but this
knob is needed to turn on bridge vlan snooping. It is disabled by
default. To enable the knob vlan filtering must also be enabled, it
doesn't make sense to have vlan mcast snooping without vlan filtering
since that would lead to inconsistencies. Disabling vlan filtering will
also automatically disable vlan mcast snooping.

Signed-off-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/if_bridge.h
net/bridge/br.c
net/bridge/br_device.c
net/bridge/br_input.c
net/bridge/br_multicast.c
net/bridge/br_private.h
net/bridge/br_vlan.c

index 6b56a7549531c42da3e9884958ac664cdcf75638..7927ad80ee861020855f5d9a616f21df942140ef 100644 (file)
@@ -720,12 +720,14 @@ struct br_mcast_stats {
 
 /* bridge boolean options
  * BR_BOOLOPT_NO_LL_LEARN - disable learning from link-local packets
+ * BR_BOOLOPT_MCAST_VLAN_SNOOPING - control vlan multicast snooping
  *
  * IMPORTANT: if adding a new option do not forget to handle
  *            it in br_boolopt_toggle/get and bridge sysfs
  */
 enum br_boolopt_id {
        BR_BOOLOPT_NO_LL_LEARN,
+       BR_BOOLOPT_MCAST_VLAN_SNOOPING,
        BR_BOOLOPT_MAX
 };
 
index ef743f94254d76362ba15f60bccddd57e3fb6798..51f2e25c4cd615f19f3d5e78ece937a2ef4f1dab 100644 (file)
@@ -214,17 +214,22 @@ static struct notifier_block br_switchdev_notifier = {
 int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
                      struct netlink_ext_ack *extack)
 {
+       int err = 0;
+
        switch (opt) {
        case BR_BOOLOPT_NO_LL_LEARN:
                br_opt_toggle(br, BROPT_NO_LL_LEARN, on);
                break;
+       case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
+               err = br_multicast_toggle_vlan_snooping(br, on, extack);
+               break;
        default:
                /* shouldn't be called with unsupported options */
                WARN_ON(1);
                break;
        }
 
-       return 0;
+       return err;
 }
 
 int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
@@ -232,6 +237,8 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
        switch (opt) {
        case BR_BOOLOPT_NO_LL_LEARN:
                return br_opt_get(br, BROPT_NO_LL_LEARN);
+       case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
+               return br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED);
        default:
                /* shouldn't be called with unsupported options */
                WARN_ON(1);
index e815bf4f9f24f77c964a8bd2e35a2a11e30b4456..00daf35f54d534990b6416032cb7cad525cabbec 100644 (file)
@@ -27,12 +27,14 @@ EXPORT_SYMBOL_GPL(nf_br_ops);
 /* net device transmit always called with BH disabled */
 netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
 {
+       struct net_bridge_mcast_port *pmctx_null = NULL;
        struct net_bridge *br = netdev_priv(dev);
        struct net_bridge_mcast *brmctx = &br->multicast_ctx;
        struct net_bridge_fdb_entry *dst;
        struct net_bridge_mdb_entry *mdst;
        const struct nf_br_ops *nf_ops;
        u8 state = BR_STATE_FORWARDING;
+       struct net_bridge_vlan *vlan;
        const unsigned char *dest;
        u16 vid = 0;
 
@@ -54,7 +56,8 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
        skb_reset_mac_header(skb);
        skb_pull(skb, ETH_HLEN);
 
-       if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid, &state))
+       if (!br_allowed_ingress(br, br_vlan_group_rcu(br), skb, &vid,
+                               &state, &vlan))
                goto out;
 
        if (IS_ENABLED(CONFIG_INET) &&
@@ -83,7 +86,7 @@ netdev_tx_t br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
                        br_flood(br, skb, BR_PKT_MULTICAST, false, true);
                        goto out;
                }
-               if (br_multicast_rcv(brmctx, NULL, skb, vid)) {
+               if (br_multicast_rcv(&brmctx, &pmctx_null, vlan, skb, vid)) {
                        kfree_skb(skb);
                        goto out;
                }
index bb2036dd4934e1fb541ad39ef55661bcc62b4d83..8a0c0cc55cb4e59a83cf20d3f9b4de8e46b3a044 100644 (file)
@@ -73,6 +73,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
        struct net_bridge_mdb_entry *mdst;
        bool local_rcv, mcast_hit = false;
        struct net_bridge_mcast *brmctx;
+       struct net_bridge_vlan *vlan;
        struct net_bridge *br;
        u16 vid = 0;
        u8 state;
@@ -84,7 +85,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
        pmctx = &p->multicast_ctx;
        state = p->state;
        if (!br_allowed_ingress(p->br, nbp_vlan_group_rcu(p), skb, &vid,
-                               &state))
+                               &state, &vlan))
                goto out;
 
        nbp_switchdev_frame_mark(p, skb);
@@ -102,7 +103,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
                        local_rcv = true;
                } else {
                        pkt_type = BR_PKT_MULTICAST;
-                       if (br_multicast_rcv(brmctx, pmctx, skb, vid))
+                       if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
                                goto drop;
                }
        }
index ef4e7de3f18d47a1f99784b5158dbc7161789226..b71772828b238904336a0b812764907a112c48c8 100644 (file)
@@ -1797,9 +1797,9 @@ void br_multicast_enable_port(struct net_bridge_port *port)
 {
        struct net_bridge *br = port->br;
 
-       spin_lock(&br->multicast_lock);
+       spin_lock_bh(&br->multicast_lock);
        __br_multicast_enable_port_ctx(&port->multicast_ctx);
-       spin_unlock(&br->multicast_lock);
+       spin_unlock_bh(&br->multicast_lock);
 }
 
 static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
@@ -1827,9 +1827,9 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
 
 void br_multicast_disable_port(struct net_bridge_port *port)
 {
-       spin_lock(&port->br->multicast_lock);
+       spin_lock_bh(&port->br->multicast_lock);
        __br_multicast_disable_port_ctx(&port->multicast_ctx);
-       spin_unlock(&port->br->multicast_lock);
+       spin_unlock_bh(&port->br->multicast_lock);
 }
 
 static int __grp_src_delete_marked(struct net_bridge_port_group *pg)
@@ -3510,8 +3510,9 @@ static int br_multicast_ipv6_rcv(struct net_bridge_mcast *brmctx,
 }
 #endif
 
-int br_multicast_rcv(struct net_bridge_mcast *brmctx,
-                    struct net_bridge_mcast_port *pmctx,
+int br_multicast_rcv(struct net_bridge_mcast **brmctx,
+                    struct net_bridge_mcast_port **pmctx,
+                    struct net_bridge_vlan *vlan,
                     struct sk_buff *skb, u16 vid)
 {
        int ret = 0;
@@ -3519,16 +3520,36 @@ int br_multicast_rcv(struct net_bridge_mcast *brmctx,
        BR_INPUT_SKB_CB(skb)->igmp = 0;
        BR_INPUT_SKB_CB(skb)->mrouters_only = 0;
 
-       if (!br_opt_get(brmctx->br, BROPT_MULTICAST_ENABLED))
+       if (!br_opt_get((*brmctx)->br, BROPT_MULTICAST_ENABLED))
                return 0;
 
+       if (br_opt_get((*brmctx)->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) && vlan) {
+               const struct net_bridge_vlan *masterv;
+
+               /* the vlan has the master flag set only when transmitting
+                * through the bridge device
+                */
+               if (br_vlan_is_master(vlan)) {
+                       masterv = vlan;
+                       *brmctx = &vlan->br_mcast_ctx;
+                       *pmctx = NULL;
+               } else {
+                       masterv = vlan->brvlan;
+                       *brmctx = &vlan->brvlan->br_mcast_ctx;
+                       *pmctx = &vlan->port_mcast_ctx;
+               }
+
+               if (!(masterv->priv_flags & BR_VLFLAG_GLOBAL_MCAST_ENABLED))
+                       return 0;
+       }
+
        switch (skb->protocol) {
        case htons(ETH_P_IP):
-               ret = br_multicast_ipv4_rcv(brmctx, pmctx, skb, vid);
+               ret = br_multicast_ipv4_rcv(*brmctx, *pmctx, skb, vid);
                break;
 #if IS_ENABLED(CONFIG_IPV6)
        case htons(ETH_P_IPV6):
-               ret = br_multicast_ipv6_rcv(brmctx, pmctx, skb, vid);
+               ret = br_multicast_ipv6_rcv(*brmctx, *pmctx, skb, vid);
                break;
 #endif
        }
@@ -3727,20 +3748,22 @@ static void __br_multicast_open(struct net_bridge_mcast *brmctx)
 
 void br_multicast_open(struct net_bridge *br)
 {
-       struct net_bridge_vlan_group *vg;
-       struct net_bridge_vlan *vlan;
-
        ASSERT_RTNL();
 
-       vg = br_vlan_group(br);
-       if (vg) {
-               list_for_each_entry(vlan, &vg->vlan_list, vlist) {
-                       struct net_bridge_mcast *brmctx;
-
-                       brmctx = &vlan->br_mcast_ctx;
-                       if (br_vlan_is_brentry(vlan) &&
-                           !br_multicast_ctx_vlan_disabled(brmctx))
-                               __br_multicast_open(&vlan->br_mcast_ctx);
+       if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
+               struct net_bridge_vlan_group *vg;
+               struct net_bridge_vlan *vlan;
+
+               vg = br_vlan_group(br);
+               if (vg) {
+                       list_for_each_entry(vlan, &vg->vlan_list, vlist) {
+                               struct net_bridge_mcast *brmctx;
+
+                               brmctx = &vlan->br_mcast_ctx;
+                               if (br_vlan_is_brentry(vlan) &&
+                                   !br_multicast_ctx_vlan_disabled(brmctx))
+                                       __br_multicast_open(&vlan->br_mcast_ctx);
+                       }
                }
        }
 
@@ -3804,22 +3827,80 @@ void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on)
        }
 }
 
-void br_multicast_stop(struct net_bridge *br)
+void br_multicast_toggle_vlan(struct net_bridge_vlan *vlan, bool on)
+{
+       struct net_bridge_port *p;
+
+       if (WARN_ON_ONCE(!br_vlan_is_master(vlan)))
+               return;
+
+       list_for_each_entry(p, &vlan->br->port_list, list) {
+               struct net_bridge_vlan *vport;
+
+               vport = br_vlan_find(nbp_vlan_group(p), vlan->vid);
+               if (!vport)
+                       continue;
+               br_multicast_toggle_one_vlan(vport, on);
+       }
+}
+
+int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
+                                     struct netlink_ext_ack *extack)
 {
        struct net_bridge_vlan_group *vg;
        struct net_bridge_vlan *vlan;
+       struct net_bridge_port *p;
 
-       ASSERT_RTNL();
+       if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) == on)
+               return 0;
+
+       if (on && !br_opt_get(br, BROPT_VLAN_ENABLED)) {
+               NL_SET_ERR_MSG_MOD(extack, "Cannot enable multicast vlan snooping with vlan filtering disabled");
+               return -EINVAL;
+       }
 
        vg = br_vlan_group(br);
-       if (vg) {
-               list_for_each_entry(vlan, &vg->vlan_list, vlist) {
-                       struct net_bridge_mcast *brmctx;
-
-                       brmctx = &vlan->br_mcast_ctx;
-                       if (br_vlan_is_brentry(vlan) &&
-                           !br_multicast_ctx_vlan_disabled(brmctx))
-                               __br_multicast_stop(&vlan->br_mcast_ctx);
+       if (!vg)
+               return 0;
+
+       br_opt_toggle(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED, on);
+
+       /* disable/enable non-vlan mcast contexts based on vlan snooping */
+       if (on)
+               __br_multicast_stop(&br->multicast_ctx);
+       else
+               __br_multicast_open(&br->multicast_ctx);
+       list_for_each_entry(p, &br->port_list, list) {
+               if (on)
+                       br_multicast_disable_port(p);
+               else
+                       br_multicast_enable_port(p);
+       }
+
+       list_for_each_entry(vlan, &vg->vlan_list, vlist)
+               br_multicast_toggle_vlan(vlan, on);
+
+       return 0;
+}
+
+void br_multicast_stop(struct net_bridge *br)
+{
+       ASSERT_RTNL();
+
+       if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
+               struct net_bridge_vlan_group *vg;
+               struct net_bridge_vlan *vlan;
+
+               vg = br_vlan_group(br);
+               if (vg) {
+                       list_for_each_entry(vlan, &vg->vlan_list, vlist) {
+                               struct net_bridge_mcast *brmctx;
+
+                               brmctx = &vlan->br_mcast_ctx;
+                               if (br_vlan_is_brentry(vlan) &&
+                                   !br_multicast_ctx_vlan_disabled(brmctx))
+                                       __br_multicast_stop(&vlan->br_mcast_ctx);
+                       }
                }
        }
 
index 5588f2d3546faad26db8be3290cbb05058cc6472..c3c2f19d3b71e146a04a0cff3fc20756b3bdd2a8 100644 (file)
@@ -433,6 +433,7 @@ enum net_bridge_opts {
        BROPT_VLAN_STATS_PER_PORT,
        BROPT_NO_LL_LEARN,
        BROPT_VLAN_BRIDGE_BINDING,
+       BROPT_MCAST_VLAN_SNOOPING_ENABLED,
 };
 
 struct net_bridge {
@@ -829,8 +830,9 @@ int br_ioctl_deviceless_stub(struct net *net, unsigned int cmd,
 
 /* br_multicast.c */
 #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
-int br_multicast_rcv(struct net_bridge_mcast *brmctx,
-                    struct net_bridge_mcast_port *pmctx,
+int br_multicast_rcv(struct net_bridge_mcast **brmctx,
+                    struct net_bridge_mcast_port **pmctx,
+                    struct net_bridge_vlan *vlan,
                     struct sk_buff *skb, u16 vid);
 struct net_bridge_mdb_entry *br_mdb_get(struct net_bridge_mcast *brmctx,
                                        struct sk_buff *skb, u16 vid);
@@ -904,6 +906,9 @@ void br_multicast_port_ctx_init(struct net_bridge_port *port,
                                struct net_bridge_mcast_port *pmctx);
 void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx);
 void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan, bool on);
+void br_multicast_toggle_vlan(struct net_bridge_vlan *vlan, bool on);
+int br_multicast_toggle_vlan_snooping(struct net_bridge *br, bool on,
+                                     struct netlink_ext_ack *extack);
 
 static inline bool br_group_is_l2(const struct br_ip *group)
 {
@@ -1090,7 +1095,8 @@ br_multicast_port_ctx_get_global(const struct net_bridge_mcast_port *pmctx)
 static inline bool
 br_multicast_ctx_vlan_global_disabled(const struct net_bridge_mcast *brmctx)
 {
-       return br_multicast_ctx_is_vlan(brmctx) &&
+       return br_opt_get(brmctx->br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+              br_multicast_ctx_is_vlan(brmctx) &&
               !(brmctx->vlan->priv_flags & BR_VLFLAG_GLOBAL_MCAST_ENABLED);
 }
 
@@ -1108,8 +1114,9 @@ br_multicast_port_ctx_vlan_disabled(const struct net_bridge_mcast_port *pmctx)
               !(pmctx->vlan->priv_flags & BR_VLFLAG_MCAST_ENABLED);
 }
 #else
-static inline int br_multicast_rcv(struct net_bridge_mcast *brmctx,
-                                  struct net_bridge_mcast_port *pmctx,
+static inline int br_multicast_rcv(struct net_bridge_mcast **brmctx,
+                                  struct net_bridge_mcast_port **pmctx,
+                                  struct net_bridge_vlan *vlan,
                                   struct sk_buff *skb,
                                   u16 vid)
 {
@@ -1245,13 +1252,26 @@ static inline void br_multicast_toggle_one_vlan(struct net_bridge_vlan *vlan,
                                                bool on)
 {
 }
+
+static inline void br_multicast_toggle_vlan(struct net_bridge_vlan *vlan,
+                                           bool on)
+{
+}
+
+static inline int br_multicast_toggle_vlan_snooping(struct net_bridge *br,
+                                                   bool on,
+                                                   struct netlink_ext_ack *extack)
+{
+       return -EOPNOTSUPP;
+}
 #endif
 
 /* br_vlan.c */
 #ifdef CONFIG_BRIDGE_VLAN_FILTERING
 bool br_allowed_ingress(const struct net_bridge *br,
                        struct net_bridge_vlan_group *vg, struct sk_buff *skb,
-                       u16 *vid, u8 *state);
+                       u16 *vid, u8 *state,
+                       struct net_bridge_vlan **vlan);
 bool br_allowed_egress(struct net_bridge_vlan_group *vg,
                       const struct sk_buff *skb);
 bool br_should_learn(struct net_bridge_port *p, struct sk_buff *skb, u16 *vid);
@@ -1363,8 +1383,11 @@ static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
 static inline bool br_allowed_ingress(const struct net_bridge *br,
                                      struct net_bridge_vlan_group *vg,
                                      struct sk_buff *skb,
-                                     u16 *vid, u8 *state)
+                                     u16 *vid, u8 *state,
+                                     struct net_bridge_vlan **vlan)
+
 {
+       *vlan = NULL;
        return true;
 }
 
index 1a8cb2b1b7621b550334dad3776684a7b004d89e..ab4969a4a3804c4b481abe308fbfb5e6a134e25c 100644 (file)
@@ -481,7 +481,8 @@ out:
 static bool __allowed_ingress(const struct net_bridge *br,
                              struct net_bridge_vlan_group *vg,
                              struct sk_buff *skb, u16 *vid,
-                             u8 *state)
+                             u8 *state,
+                             struct net_bridge_vlan **vlan)
 {
        struct pcpu_sw_netstats *stats;
        struct net_bridge_vlan *v;
@@ -546,8 +547,9 @@ static bool __allowed_ingress(const struct net_bridge *br,
                         */
                        skb->vlan_tci |= pvid;
 
-               /* if stats are disabled we can avoid the lookup */
-               if (!br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
+               /* if snooping and stats are disabled we can avoid the lookup */
+               if (!br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED) &&
+                   !br_opt_get(br, BROPT_VLAN_STATS_ENABLED)) {
                        if (*state == BR_STATE_FORWARDING) {
                                *state = br_vlan_get_pvid_state(vg);
                                return br_vlan_state_allowed(*state, true);
@@ -574,6 +576,8 @@ static bool __allowed_ingress(const struct net_bridge *br,
                u64_stats_update_end(&stats->syncp);
        }
 
+       *vlan = v;
+
        return true;
 
 drop:
@@ -583,17 +587,19 @@ drop:
 
 bool br_allowed_ingress(const struct net_bridge *br,
                        struct net_bridge_vlan_group *vg, struct sk_buff *skb,
-                       u16 *vid, u8 *state)
+                       u16 *vid, u8 *state,
+                       struct net_bridge_vlan **vlan)
 {
        /* If VLAN filtering is disabled on the bridge, all packets are
         * permitted.
         */
+       *vlan = NULL;
        if (!br_opt_get(br, BROPT_VLAN_ENABLED)) {
                BR_INPUT_SKB_CB(skb)->vlan_filtered = false;
                return true;
        }
 
-       return __allowed_ingress(br, vg, skb, vid, state);
+       return __allowed_ingress(br, vg, skb, vid, state, vlan);
 }
 
 /* Called under RCU. */
@@ -834,6 +840,10 @@ int br_vlan_filter_toggle(struct net_bridge *br, unsigned long val,
        br_manage_promisc(br);
        recalculate_group_addr(br);
        br_recalculate_fwd_mask(br);
+       if (!val && br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
+               br_info(br, "vlan filtering disabled, automatically disabling multicast vlan snooping\n");
+               br_multicast_toggle_vlan_snooping(br, false, NULL);
+       }
 
        return 0;
 }