net: bridge: add vlan mcast snooping knob

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>
This commit is contained in:
Nikolay Aleksandrov 2021-07-19 20:06:28 +03:00 committed by David S. Miller
parent 7b54aaaf53
commit f4b7002a70
7 changed files with 173 additions and 46 deletions

View 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
};

View 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);

View 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;
}

View 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;
}
}

View 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;
if (br_opt_get(br, BROPT_MCAST_VLAN_SNOOPING_ENABLED)) {
struct net_bridge_vlan_group *vg;
struct net_bridge_vlan *vlan;
brmctx = &vlan->br_mcast_ctx;
if (br_vlan_is_brentry(vlan) &&
!br_multicast_ctx_vlan_disabled(brmctx))
__br_multicast_open(&vlan->br_mcast_ctx);
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;
if (!vg)
return 0;
brmctx = &vlan->br_mcast_ctx;
if (br_vlan_is_brentry(vlan) &&
!br_multicast_ctx_vlan_disabled(brmctx))
__br_multicast_stop(&vlan->br_mcast_ctx);
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);
}
}
}

View 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;
}

View 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;
}