Skip to content

Commit

Permalink
net: bridge: add vlan mcast snooping knob
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Nikolay Aleksandrov authored and davem330 committed Jul 20, 2021
1 parent 7b54aaa commit f4b7002
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 48 deletions.
2 changes: 2 additions & 0 deletions include/uapi/linux/if_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Expand Down
9 changes: 8 additions & 1 deletion net/bridge/br.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,24 +214,31 @@ 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)
{
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);
Expand Down
7 changes: 5 additions & 2 deletions net/bridge/br_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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) &&
Expand Down Expand Up @@ -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;
}
Expand Down
5 changes: 3 additions & 2 deletions net/bridge/br_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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;
}
}
Expand Down
143 changes: 112 additions & 31 deletions net/bridge/br_multicast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -3510,25 +3510,46 @@ 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;

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
}
Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Loading

0 comments on commit f4b7002

Please sign in to comment.