diff mbox series

[2/5] net: bridge: Implement bridge flood flag

Message ID 20220317065031.3830481-3-mattias.forsblad@gmail.com (mailing list archive)
State Not Applicable
Delegated to: Netdev Maintainers
Headers show
Series bridge: dsa: switchdev: mv88e6xxx: Implement bridge flood flags | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Series has a cover letter
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 16 this patch: 16
netdev/cc_maintainers warning 2 maintainers not CCed: bridge@lists.linux-foundation.org razor@blackwall.org
netdev/build_clang success Errors and warnings before: 19 this patch: 19
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 21 this patch: 21
netdev/checkpatch warning WARNING: line length of 86 exceeds 80 columns
netdev/kdoc success Errors and warnings before: 3 this patch: 3
netdev/source_inline success Was 0 now: 0

Commit Message

Mattias Forsblad March 17, 2022, 6:50 a.m. UTC
This patch implements the bridge flood flags. There are three different
flags matching unicast, multicast and broadcast. When the corresponding
flag is cleared packets received on bridge ports will not be flooded
towards the bridge.
This makes is possible to only forward selected traffic between the
port members of the bridge.

Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
---
 include/linux/if_bridge.h      |  6 +++++
 include/uapi/linux/if_bridge.h |  9 ++++++-
 net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
 net/bridge/br_device.c         |  3 +++
 net/bridge/br_input.c          | 23 ++++++++++++++---
 net/bridge/br_private.h        |  4 +++
 6 files changed, 85 insertions(+), 5 deletions(-)

Comments

Joachim Wiberg March 17, 2022, 9:07 a.m. UTC | #1
On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
> This patch implements the bridge flood flags. There are three different
> flags matching unicast, multicast and broadcast. When the corresponding
> flag is cleared packets received on bridge ports will not be flooded
> towards the bridge.

If I've not completely misunderstood things, I believe the flood and
mcast_flood flags operate on unknown unicast and multicast.  With that
in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
add my own comments below.

Happy incident I saw this patch set, I have a very similar one for these
flags to the bridge itself, with the intent to improve handling of all
classes of multicast to/from the bridge itself.

> [snip]
> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> index e0c13fcc50ed..fcb0757bfdcc 100644
> --- a/net/bridge/br_input.c
> +++ b/net/bridge/br_input.c
> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  		/* by definition the broadcast is also a multicast address */
>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>  			pkt_type = BR_PKT_BROADCAST;
> -			local_rcv = true;
> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);

Minor comment, I believe the preferred style is more like this:

	if (br_opt_get(br, BROPT_BCAST_FLOOD))
        	local_rcv = true;

>  		} else {
>  			pkt_type = BR_PKT_MULTICAST;
> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> -				goto drop;
> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> +					goto drop;

Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
we cannot bypass the call to br_multicast_rcv(), which helps with the
classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
router ports, while the mdb lookup (below) is what an tell us if we
have uknown multicast and there we can check the BROPT_MCAST_FLOOD
flag for the bridge itself.

>  		}
>  	}
>  
> @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  			local_rcv = true;
>  			br->dev->stats.multicast++;
>  		}
> +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
> +			local_rcv = false;

We should never set local_rcv to false, only ever use constructs that
set it to true.  Here the PROMISC flag (above) condition would be
negated, which would be a regression.

Instead, for multicast I believe we should ensure that we reach the
else statement for unknown IP multicast, preventing mcast_hit from
being set, and instead flood unknown multicast using br_flood().

This is a bigger change that involves:

  1) dropping the mrouters_only skb flag for unknown multicast,
     keeping it only for IGMP/MLD reports
  2) extending br_flood() slightly to flood unknown multicast
     also to mcast_router ports

As I mentioned above, I have some patches, including selftests, for
forwarding known/unknown multicast using the mdb and mcast_flood +
mcast_router flags.  Maybe we should combine efforts here somehow?

>  		break;
>  	case BR_PKT_UNICAST:
>  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
> +		if (!br_opt_get(br, BROPT_FLOOD))
> +			local_rcv = false;

Again, never set it to false, invert the check instead, like this:

		if (!dst && br_opt_get(br, BROPT_FLOOD))
			local_rcv = true;

>  		break;
>  	default:
>  		break;
> @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  	if (dst) {
>  		unsigned long now = jiffies;
>  
> -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
> +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
>  			return br_pass_frame_up(skb);

I believe this would break both the flooding of unknown multicast and
the PROMISC case.  Down here we are broadcast or known/unknown multicast
land, so the local_rcv flag should be sufficient.

>  		if (now != dst->used)
> @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  }
>  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
>  
> +bool br_flood_enabled(const struct net_device *dev)
> +{
> +	struct net_bridge *br = netdev_priv(dev);
> +
> +	return !!(br_opt_get(br, BROPT_FLOOD) ||
> +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
> +		   br_opt_get(br, BROPT_BCAST_FLOOD));

Minor nit, don't know what the rest of the list feels about this, but
maybe the BROPT_FLOOD option should be renamed to BR_UCAST_FLOOD or
BR_UNICAST_FLOOD?

Best regards
 /Joachim
Nikolay Aleksandrov March 17, 2022, 10:11 a.m. UTC | #2
On 17/03/2022 08:50, Mattias Forsblad wrote:
> This patch implements the bridge flood flags. There are three different
> flags matching unicast, multicast and broadcast. When the corresponding
> flag is cleared packets received on bridge ports will not be flooded
> towards the bridge.
> This makes is possible to only forward selected traffic between the
> port members of the bridge.
> 
> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> ---
>  include/linux/if_bridge.h      |  6 +++++
>  include/uapi/linux/if_bridge.h |  9 ++++++-
>  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
>  net/bridge/br_device.c         |  3 +++
>  net/bridge/br_input.c          | 23 ++++++++++++++---
>  net/bridge/br_private.h        |  4 +++
>  6 files changed, 85 insertions(+), 5 deletions(-)
> 
Please always CC bridge maintainers for bridge patches.

I almost missed this one. I'll add my reply to Joachim's notes
which are pretty spot on.

> diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
> index 3aae023a9353..fa8e000a6fb9 100644
> --- a/include/linux/if_bridge.h
> +++ b/include/linux/if_bridge.h
> @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
>  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
>  				    const unsigned char *addr,
>  				    __u16 vid);
> +bool br_flood_enabled(const struct net_device *dev);
>  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
>  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
>  u8 br_port_get_stp_state(const struct net_device *dev);
> @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
>  	return NULL;
>  }
>  
> +static inline bool br_flood_enabled(const struct net_device *dev)
> +{
> +	return true;
> +}
> +
>  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
>  {
>  }
> diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
> index 2711c3522010..765ed70c9b28 100644
> --- a/include/uapi/linux/if_bridge.h
> +++ b/include/uapi/linux/if_bridge.h
> @@ -72,6 +72,7 @@ struct __bridge_info {
>  	__u32 tcn_timer_value;
>  	__u32 topology_change_timer_value;
>  	__u32 gc_timer_value;
> +	__u8 flood;
>  };
>  
>  struct __port_info {
> @@ -752,13 +753,19 @@ 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
> + * BR_BOOLOPT_FLOOD - control bridge flood flag
> + * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
> + * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
>   *
>   * IMPORTANT: if adding a new option do not forget to handle
> - *            it in br_boolopt_toggle/get and bridge sysfs
> + *            it in br_boolopt_toggle/get
>   */
>  enum br_boolopt_id {
>  	BR_BOOLOPT_NO_LL_LEARN,
>  	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
> +	BR_BOOLOPT_FLOOD,
> +	BR_BOOLOPT_MCAST_FLOOD,
> +	BR_BOOLOPT_BCAST_FLOOD,
>  	BR_BOOLOPT_MAX
>  };
>  
> diff --git a/net/bridge/br.c b/net/bridge/br.c
> index b1dea3febeea..63a17bed6c63 100644
> --- a/net/bridge/br.c
> +++ b/net/bridge/br.c
> @@ -265,6 +265,11 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
>  	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
>  		err = br_multicast_toggle_vlan_snooping(br, on, extack);
>  		break;
> +	case BR_BOOLOPT_FLOOD:
> +	case BR_BOOLOPT_MCAST_FLOOD:
> +	case BR_BOOLOPT_BCAST_FLOOD:
> +		err = br_flood_toggle(br, opt, on);
> +		break;
>  	default:
>  		/* shouldn't be called with unsupported options */
>  		WARN_ON(1);
> @@ -281,6 +286,12 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
>  		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);
> +	case BR_BOOLOPT_FLOOD:
> +		return br_opt_get(br, BROPT_FLOOD);
> +	case BR_BOOLOPT_MCAST_FLOOD:
> +		return br_opt_get(br, BROPT_MCAST_FLOOD);
> +	case BR_BOOLOPT_BCAST_FLOOD:
> +		return br_opt_get(br, BROPT_BCAST_FLOOD);
>  	default:
>  		/* shouldn't be called with unsupported options */
>  		WARN_ON(1);
> @@ -325,6 +336,40 @@ void br_boolopt_multi_get(const struct net_bridge *br,
>  	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
>  }
>  
> +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
> +		    bool on)
> +{
> +	struct switchdev_attr attr = {
> +		.orig_dev = br->dev,
> +		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
> +		.flags = SWITCHDEV_F_DEFER,
> +	};
> +	enum net_bridge_opts bropt;
> +	int ret;
> +
> +	switch (opt) {
> +	case BR_BOOLOPT_FLOOD:
> +		bropt = BROPT_FLOOD;
> +		break;
> +	case BR_BOOLOPT_MCAST_FLOOD:
> +		bropt = BROPT_MCAST_FLOOD;
> +		break;
> +	case BR_BOOLOPT_BCAST_FLOOD:
> +		bropt = BROPT_BCAST_FLOOD;
> +		break;
> +	default:
> +		WARN_ON(1);
> +		return -EINVAL;
> +	}
> +	br_opt_toggle(br, bropt, on);
> +
> +	attr.u.brport_flags.mask = BIT(bropt);
> +	attr.u.brport_flags.val = on << bropt;
> +	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
> +
> +	return ret;
> +}
> +
>  /* private bridge options, controlled by the kernel */
>  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
>  {
> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> index 8d6bab244c4a..fafaef9d4b3a 100644
> --- a/net/bridge/br_device.c
> +++ b/net/bridge/br_device.c
> @@ -524,6 +524,9 @@ void br_dev_setup(struct net_device *dev)
>  	br->bridge_hello_time = br->hello_time = 2 * HZ;
>  	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
>  	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
> +	br_opt_toggle(br, BROPT_FLOOD, true);
> +	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
> +	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
>  	dev->max_mtu = ETH_MAX_MTU;
>  
>  	br_netfilter_rtable_init(br);
> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> index e0c13fcc50ed..fcb0757bfdcc 100644
> --- a/net/bridge/br_input.c
> +++ b/net/bridge/br_input.c
> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  		/* by definition the broadcast is also a multicast address */
>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>  			pkt_type = BR_PKT_BROADCAST;
> -			local_rcv = true;
> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
>  		} else {
>  			pkt_type = BR_PKT_MULTICAST;
> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> -				goto drop;
> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> +					goto drop;
>  		}
>  	}
>  
> @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  			local_rcv = true;
>  			br->dev->stats.multicast++;
>  		}
> +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
> +			local_rcv = false;
>  		break;
>  	case BR_PKT_UNICAST:
>  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
> +		if (!br_opt_get(br, BROPT_FLOOD))
> +			local_rcv = false;
>  		break;
>  	default:
>  		break;
> @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  	if (dst) {
>  		unsigned long now = jiffies;
>  
> -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
> +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
>  			return br_pass_frame_up(skb);
>  
>  		if (now != dst->used)
> @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>  }
>  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
>  
> +bool br_flood_enabled(const struct net_device *dev)
> +{
> +	struct net_bridge *br = netdev_priv(dev);
> +
> +	return !!(br_opt_get(br, BROPT_FLOOD) ||
> +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
> +		   br_opt_get(br, BROPT_BCAST_FLOOD));
> +}
> +EXPORT_SYMBOL_GPL(br_flood_enabled);
> +
>  static void __br_handle_local_finish(struct sk_buff *skb)
>  {
>  	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
> diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> index 48bc61ebc211..cf88dce0b92b 100644
> --- a/net/bridge/br_private.h
> +++ b/net/bridge/br_private.h
> @@ -445,6 +445,9 @@ enum net_bridge_opts {
>  	BROPT_NO_LL_LEARN,
>  	BROPT_VLAN_BRIDGE_BINDING,
>  	BROPT_MCAST_VLAN_SNOOPING_ENABLED,
> +	BROPT_FLOOD,
> +	BROPT_MCAST_FLOOD,
> +	BROPT_BCAST_FLOOD,
>  };
>  
>  struct net_bridge {
> @@ -720,6 +723,7 @@ int br_boolopt_multi_toggle(struct net_bridge *br,
>  void br_boolopt_multi_get(const struct net_bridge *br,
>  			  struct br_boolopt_multi *bm);
>  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on);
> +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on);
>  
>  /* br_device.c */
>  void br_dev_setup(struct net_device *dev);
Nikolay Aleksandrov March 17, 2022, 10:30 a.m. UTC | #3
On 17/03/2022 11:07, Joachim Wiberg wrote:
> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
>> This patch implements the bridge flood flags. There are three different
>> flags matching unicast, multicast and broadcast. When the corresponding
>> flag is cleared packets received on bridge ports will not be flooded
>> towards the bridge.
> 
> If I've not completely misunderstood things, I believe the flood and
> mcast_flood flags operate on unknown unicast and multicast.  With that
> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
> add my own comments below.
> 
> Happy incident I saw this patch set, I have a very similar one for these
> flags to the bridge itself, with the intent to improve handling of all
> classes of multicast to/from the bridge itself.

+1

I'll add my comments below, yours are pretty spot on. I have one more
that's for the snipped part, I'll send that separately. :)

First please split this into 3 separate patches - one for each flag.
That would make reviewing much easier.

> >> [snip]
>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>> index e0c13fcc50ed..fcb0757bfdcc 100644
>> --- a/net/bridge/br_input.c
>> +++ b/net/bridge/br_input.c
>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>  		/* by definition the broadcast is also a multicast address */
>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>>  			pkt_type = BR_PKT_BROADCAST;
>> -			local_rcv = true;
>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> 
> Minor comment, I believe the preferred style is more like this:
> 
> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
>         	local_rcv = true;
> 

ack

>>  		} else {
>>  			pkt_type = BR_PKT_MULTICAST;
>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> -				goto drop;
>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> +					goto drop;
> 
> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
> we cannot bypass the call to br_multicast_rcv(), which helps with the
> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
> router ports, while the mdb lookup (below) is what an tell us if we
> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
> flag for the bridge itself.
> 

+1

>>  		}
>>  	}
>>  
>> @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>  			local_rcv = true;
>>  			br->dev->stats.multicast++;
>>  		}
>> +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
>> +			local_rcv = false;
> 
> We should never set local_rcv to false, only ever use constructs that
> set it to true.  Here the PROMISC flag (above) condition would be
> negated, which would be a regression.
> 
> Instead, for multicast I believe we should ensure that we reach the
> else statement for unknown IP multicast, preventing mcast_hit from
> being set, and instead flood unknown multicast using br_flood().
> 
> This is a bigger change that involves:
> 
>   1) dropping the mrouters_only skb flag for unknown multicast,
>      keeping it only for IGMP/MLD reports
>   2) extending br_flood() slightly to flood unknown multicast
>      also to mcast_router ports
> 
> As I mentioned above, I have some patches, including selftests, for
> forwarding known/unknown multicast using the mdb and mcast_flood +
> mcast_router flags.  Maybe we should combine efforts here somehow?
> 

Ack, sounds good! Please coordinate that between yourselves, if the mcast
flood flag will be dropped from this set or if Joachim's will be integrated.

>>  		break;
>>  	case BR_PKT_UNICAST:
>>  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
>> +		if (!br_opt_get(br, BROPT_FLOOD))
>> +			local_rcv = false;
> 
> Again, never set it to false, invert the check instead, like this:
> 
> 		if (!dst && br_opt_get(br, BROPT_FLOOD))
> 			local_rcv = true;
> 
>>  		break;
>>  	default:
>>  		break;
>> @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>  	if (dst) {
>>  		unsigned long now = jiffies;
>>  
>> -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
>> +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
>>  			return br_pass_frame_up(skb);
> 
> I believe this would break both the flooding of unknown multicast and
> the PROMISC case.  Down here we are broadcast or known/unknown multicast
> land, so the local_rcv flag should be sufficient.
> 
>>  		if (now != dst->used)
>> @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>  }
>>  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
>>  
>> +bool br_flood_enabled(const struct net_device *dev)
>> +{
>> +	struct net_bridge *br = netdev_priv(dev);

const

>> +
>> +	return !!(br_opt_get(br, BROPT_FLOOD) ||
>> +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
>> +		   br_opt_get(br, BROPT_BCAST_FLOOD));
> 
> Minor nit, don't know what the rest of the list feels about this, but
> maybe the BROPT_FLOOD option should be renamed to BR_UCAST_FLOOD or
> BR_UNICAST_FLOOD?
> 

Exactly, very good suggestion. Unfortunately we already have BR_FLOOD and can't
do anything about it, but we shouldn't follow that bad example.

> Best regards
>  /Joachim

Cheers,
 Nik
Nikolay Aleksandrov March 17, 2022, 10:32 a.m. UTC | #4
On 17/03/2022 12:11, Nikolay Aleksandrov wrote:
> On 17/03/2022 08:50, Mattias Forsblad wrote:
>> This patch implements the bridge flood flags. There are three different
>> flags matching unicast, multicast and broadcast. When the corresponding
>> flag is cleared packets received on bridge ports will not be flooded
>> towards the bridge.
>> This makes is possible to only forward selected traffic between the
>> port members of the bridge.
>>
>> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
>> ---
>>  include/linux/if_bridge.h      |  6 +++++
>>  include/uapi/linux/if_bridge.h |  9 ++++++-
>>  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
>>  net/bridge/br_device.c         |  3 +++
>>  net/bridge/br_input.c          | 23 ++++++++++++++---
>>  net/bridge/br_private.h        |  4 +++
>>  6 files changed, 85 insertions(+), 5 deletions(-)
>>
> Please always CC bridge maintainers for bridge patches.
> 
> I almost missed this one. I'll add my reply to Joachim's notes
> which are pretty spot on.
> 
>> diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
>> index 3aae023a9353..fa8e000a6fb9 100644
>> --- a/include/linux/if_bridge.h
>> +++ b/include/linux/if_bridge.h
>> @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
>>  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
>>  				    const unsigned char *addr,
>>  				    __u16 vid);
>> +bool br_flood_enabled(const struct net_device *dev);
>>  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
>>  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
>>  u8 br_port_get_stp_state(const struct net_device *dev);
>> @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
>>  	return NULL;
>>  }
>>  
>> +static inline bool br_flood_enabled(const struct net_device *dev)
>> +{
>> +	return true;
>> +}
>> +
>>  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
>>  {
>>  }
>> diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
>> index 2711c3522010..765ed70c9b28 100644
>> --- a/include/uapi/linux/if_bridge.h
>> +++ b/include/uapi/linux/if_bridge.h
>> @@ -72,6 +72,7 @@ struct __bridge_info {
>>  	__u32 tcn_timer_value;
>>  	__u32 topology_change_timer_value;
>>  	__u32 gc_timer_value;
>> +	__u8 flood;
>>  };

Replying to myself as this part was snipped from Joachim's comments.
You cannot change uapi structures.
Vladimir Oltean March 17, 2022, 11:15 a.m. UTC | #5
On Thu, Mar 17, 2022 at 12:11:40PM +0200, Nikolay Aleksandrov wrote:
> On 17/03/2022 08:50, Mattias Forsblad wrote:
> > This patch implements the bridge flood flags. There are three different
> > flags matching unicast, multicast and broadcast. When the corresponding
> > flag is cleared packets received on bridge ports will not be flooded
> > towards the bridge.
> > This makes is possible to only forward selected traffic between the
> > port members of the bridge.
> > 
> > Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> > ---
> >  include/linux/if_bridge.h      |  6 +++++
> >  include/uapi/linux/if_bridge.h |  9 ++++++-
> >  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
> >  net/bridge/br_device.c         |  3 +++
> >  net/bridge/br_input.c          | 23 ++++++++++++++---
> >  net/bridge/br_private.h        |  4 +++
> >  6 files changed, 85 insertions(+), 5 deletions(-)
> > 
> Please always CC bridge maintainers for bridge patches.
> I almost missed this one. I'll add my reply to Joachim's notes
> which are pretty spot on.

And DSA maintainers for DSA patches ;) I was aimlessly scrolling through
patchwork when I happened to notice these, and the series is already at v3.

As a matter of fact, I downloaded these patches from the mailing list
with the intention of giving them a spin on mv88e6xxx to see what
they're about, and to my surprise, this particular patch (I haven't even
reached the offloading part) breaks DHCP on my bridge, so it can no
longer get an IP address. I haven't toggled any bridge flag through
netlink, just booted the board with systemd-networkd. The same thing
happens with my LS1028A board. Further investigation to come, but this
isn't off to a good start, I'm afraid...

> 
> > diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
> > index 3aae023a9353..fa8e000a6fb9 100644
> > --- a/include/linux/if_bridge.h
> > +++ b/include/linux/if_bridge.h
> > @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
> >  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
> >  				    const unsigned char *addr,
> >  				    __u16 vid);
> > +bool br_flood_enabled(const struct net_device *dev);
> >  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
> >  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
> >  u8 br_port_get_stp_state(const struct net_device *dev);
> > @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
> >  	return NULL;
> >  }
> >  
> > +static inline bool br_flood_enabled(const struct net_device *dev)
> > +{
> > +	return true;
> > +}
> > +
> >  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
> >  {
> >  }
> > diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
> > index 2711c3522010..765ed70c9b28 100644
> > --- a/include/uapi/linux/if_bridge.h
> > +++ b/include/uapi/linux/if_bridge.h
> > @@ -72,6 +72,7 @@ struct __bridge_info {
> >  	__u32 tcn_timer_value;
> >  	__u32 topology_change_timer_value;
> >  	__u32 gc_timer_value;
> > +	__u8 flood;
> >  };
> >  
> >  struct __port_info {
> > @@ -752,13 +753,19 @@ 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
> > + * BR_BOOLOPT_FLOOD - control bridge flood flag
> > + * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
> > + * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
> >   *
> >   * IMPORTANT: if adding a new option do not forget to handle
> > - *            it in br_boolopt_toggle/get and bridge sysfs
> > + *            it in br_boolopt_toggle/get
> >   */
> >  enum br_boolopt_id {
> >  	BR_BOOLOPT_NO_LL_LEARN,
> >  	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
> > +	BR_BOOLOPT_FLOOD,
> > +	BR_BOOLOPT_MCAST_FLOOD,
> > +	BR_BOOLOPT_BCAST_FLOOD,
> >  	BR_BOOLOPT_MAX
> >  };
> >  
> > diff --git a/net/bridge/br.c b/net/bridge/br.c
> > index b1dea3febeea..63a17bed6c63 100644
> > --- a/net/bridge/br.c
> > +++ b/net/bridge/br.c
> > @@ -265,6 +265,11 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
> >  	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
> >  		err = br_multicast_toggle_vlan_snooping(br, on, extack);
> >  		break;
> > +	case BR_BOOLOPT_FLOOD:
> > +	case BR_BOOLOPT_MCAST_FLOOD:
> > +	case BR_BOOLOPT_BCAST_FLOOD:
> > +		err = br_flood_toggle(br, opt, on);
> > +		break;
> >  	default:
> >  		/* shouldn't be called with unsupported options */
> >  		WARN_ON(1);
> > @@ -281,6 +286,12 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
> >  		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);
> > +	case BR_BOOLOPT_FLOOD:
> > +		return br_opt_get(br, BROPT_FLOOD);
> > +	case BR_BOOLOPT_MCAST_FLOOD:
> > +		return br_opt_get(br, BROPT_MCAST_FLOOD);
> > +	case BR_BOOLOPT_BCAST_FLOOD:
> > +		return br_opt_get(br, BROPT_BCAST_FLOOD);
> >  	default:
> >  		/* shouldn't be called with unsupported options */
> >  		WARN_ON(1);
> > @@ -325,6 +336,40 @@ void br_boolopt_multi_get(const struct net_bridge *br,
> >  	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
> >  }
> >  
> > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
> > +		    bool on)
> > +{
> > +	struct switchdev_attr attr = {
> > +		.orig_dev = br->dev,
> > +		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
> > +		.flags = SWITCHDEV_F_DEFER,
> > +	};
> > +	enum net_bridge_opts bropt;
> > +	int ret;
> > +
> > +	switch (opt) {
> > +	case BR_BOOLOPT_FLOOD:
> > +		bropt = BROPT_FLOOD;
> > +		break;
> > +	case BR_BOOLOPT_MCAST_FLOOD:
> > +		bropt = BROPT_MCAST_FLOOD;
> > +		break;
> > +	case BR_BOOLOPT_BCAST_FLOOD:
> > +		bropt = BROPT_BCAST_FLOOD;
> > +		break;
> > +	default:
> > +		WARN_ON(1);
> > +		return -EINVAL;
> > +	}
> > +	br_opt_toggle(br, bropt, on);
> > +
> > +	attr.u.brport_flags.mask = BIT(bropt);
> > +	attr.u.brport_flags.val = on << bropt;
> > +	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
> > +
> > +	return ret;
> > +}
> > +
> >  /* private bridge options, controlled by the kernel */
> >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
> >  {
> > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > index 8d6bab244c4a..fafaef9d4b3a 100644
> > --- a/net/bridge/br_device.c
> > +++ b/net/bridge/br_device.c
> > @@ -524,6 +524,9 @@ void br_dev_setup(struct net_device *dev)
> >  	br->bridge_hello_time = br->hello_time = 2 * HZ;
> >  	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
> >  	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
> > +	br_opt_toggle(br, BROPT_FLOOD, true);
> > +	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
> > +	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
> >  	dev->max_mtu = ETH_MAX_MTU;
> >  
> >  	br_netfilter_rtable_init(br);
> > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > index e0c13fcc50ed..fcb0757bfdcc 100644
> > --- a/net/bridge/br_input.c
> > +++ b/net/bridge/br_input.c
> > @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >  		/* by definition the broadcast is also a multicast address */
> >  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
> >  			pkt_type = BR_PKT_BROADCAST;
> > -			local_rcv = true;
> > +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> >  		} else {
> >  			pkt_type = BR_PKT_MULTICAST;
> > -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > -				goto drop;
> > +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> > +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > +					goto drop;
> >  		}
> >  	}
> >  
> > @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >  			local_rcv = true;
> >  			br->dev->stats.multicast++;
> >  		}
> > +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
> > +			local_rcv = false;
> >  		break;
> >  	case BR_PKT_UNICAST:
> >  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
> > +		if (!br_opt_get(br, BROPT_FLOOD))
> > +			local_rcv = false;
> >  		break;
> >  	default:
> >  		break;
> > @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >  	if (dst) {
> >  		unsigned long now = jiffies;
> >  
> > -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
> > +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
> >  			return br_pass_frame_up(skb);
> >  
> >  		if (now != dst->used)
> > @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >  }
> >  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
> >  
> > +bool br_flood_enabled(const struct net_device *dev)
> > +{
> > +	struct net_bridge *br = netdev_priv(dev);
> > +
> > +	return !!(br_opt_get(br, BROPT_FLOOD) ||
> > +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
> > +		   br_opt_get(br, BROPT_BCAST_FLOOD));
> > +}
> > +EXPORT_SYMBOL_GPL(br_flood_enabled);
> > +
> >  static void __br_handle_local_finish(struct sk_buff *skb)
> >  {
> >  	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
> > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > index 48bc61ebc211..cf88dce0b92b 100644
> > --- a/net/bridge/br_private.h
> > +++ b/net/bridge/br_private.h
> > @@ -445,6 +445,9 @@ enum net_bridge_opts {
> >  	BROPT_NO_LL_LEARN,
> >  	BROPT_VLAN_BRIDGE_BINDING,
> >  	BROPT_MCAST_VLAN_SNOOPING_ENABLED,
> > +	BROPT_FLOOD,
> > +	BROPT_MCAST_FLOOD,
> > +	BROPT_BCAST_FLOOD,
> >  };
> >  
> >  struct net_bridge {
> > @@ -720,6 +723,7 @@ int br_boolopt_multi_toggle(struct net_bridge *br,
> >  void br_boolopt_multi_get(const struct net_bridge *br,
> >  			  struct br_boolopt_multi *bm);
> >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on);
> > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on);
> >  
> >  /* br_device.c */
> >  void br_dev_setup(struct net_device *dev);
>
Mattias Forsblad March 17, 2022, 11:39 a.m. UTC | #6
On 2022-03-17 10:07, Joachim Wiberg wrote:
> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
>> This patch implements the bridge flood flags. There are three different
>> flags matching unicast, multicast and broadcast. When the corresponding
>> flag is cleared packets received on bridge ports will not be flooded
>> towards the bridge.
> 
> If I've not completely misunderstood things, I believe the flood and
> mcast_flood flags operate on unknown unicast and multicast.  With that
> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
> add my own comments below.
> 
> Happy incident I saw this patch set, I have a very similar one for these
> flags to the bridge itself, with the intent to improve handling of all
> classes of multicast to/from the bridge itself.
> 
>> [snip]
>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>> index e0c13fcc50ed..fcb0757bfdcc 100644
>> --- a/net/bridge/br_input.c
>> +++ b/net/bridge/br_input.c
>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>  		/* by definition the broadcast is also a multicast address */
>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>>  			pkt_type = BR_PKT_BROADCAST;
>> -			local_rcv = true;
>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> 
> Minor comment, I believe the preferred style is more like this:
> 
> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
>         	local_rcv = true;
> 
>>  		} else {
>>  			pkt_type = BR_PKT_MULTICAST;
>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> -				goto drop;
>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> +					goto drop;
> 
> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
> we cannot bypass the call to br_multicast_rcv(), which helps with the
> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
> router ports, while the mdb lookup (below) is what an tell us if we
> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
> flag for the bridge itself.

The original flag was name was local_receive to separate it from being
mistaken for the flood unknown flags. However the comment I've got was
to align it with the existing (port) flags. These flags have nothing to do with
the port flood unknown flags. Imagine the setup below:

           vlan1
             |
            br0             br1
           /   \           /   \
         swp1 swp2       swp3 swp4

We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
On br1 we want to just forward packets between swp3/4 and disable learning. 
Additional we don't want this traffic to impact the CPU. 
If we disable learning on swp3/4 all traffic will be unknown and if we also 
have flood unknown on the CPU-port because of requirements for br0 it will
impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
with the help of the PVT.

/Mattias
Nikolay Aleksandrov March 17, 2022, 11:42 a.m. UTC | #7
On 17/03/2022 13:39, Mattias Forsblad wrote:
> On 2022-03-17 10:07, Joachim Wiberg wrote:
>> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
>>> This patch implements the bridge flood flags. There are three different
>>> flags matching unicast, multicast and broadcast. When the corresponding
>>> flag is cleared packets received on bridge ports will not be flooded
>>> towards the bridge.
>>
>> If I've not completely misunderstood things, I believe the flood and
>> mcast_flood flags operate on unknown unicast and multicast.  With that
>> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
>> add my own comments below.
>>
>> Happy incident I saw this patch set, I have a very similar one for these
>> flags to the bridge itself, with the intent to improve handling of all
>> classes of multicast to/from the bridge itself.
>>
>>> [snip]
>>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>>> index e0c13fcc50ed..fcb0757bfdcc 100644
>>> --- a/net/bridge/br_input.c
>>> +++ b/net/bridge/br_input.c
>>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>>  		/* by definition the broadcast is also a multicast address */
>>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>>>  			pkt_type = BR_PKT_BROADCAST;
>>> -			local_rcv = true;
>>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
>>
>> Minor comment, I believe the preferred style is more like this:
>>
>> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
>>         	local_rcv = true;
>>
>>>  		} else {
>>>  			pkt_type = BR_PKT_MULTICAST;
>>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>>> -				goto drop;
>>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>>> +					goto drop;
>>
>> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
>> we cannot bypass the call to br_multicast_rcv(), which helps with the
>> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
>> router ports, while the mdb lookup (below) is what an tell us if we
>> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
>> flag for the bridge itself.
> 
> The original flag was name was local_receive to separate it from being
> mistaken for the flood unknown flags. However the comment I've got was
> to align it with the existing (port) flags. These flags have nothing to do with
> the port flood unknown flags. Imagine the setup below:
> 
>            vlan1
>              |
>             br0             br1
>            /   \           /   \
>          swp1 swp2       swp3 swp4
> 
> We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
> On br1 we want to just forward packets between swp3/4 and disable learning. 
> Additional we don't want this traffic to impact the CPU. 
> If we disable learning on swp3/4 all traffic will be unknown and if we also 
> have flood unknown on the CPU-port because of requirements for br0 it will
> impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
> with the help of the PVT.
> 
> /Mattias

The feedback was correct and we all assumed unknown traffic control.
If you don't want any local receive then use filtering rules. Don't add unnecessary flags.
Vladimir Oltean March 17, 2022, 11:57 a.m. UTC | #8
On Thu, Mar 17, 2022 at 01:15:45PM +0200, Vladimir Oltean wrote:
> On Thu, Mar 17, 2022 at 12:11:40PM +0200, Nikolay Aleksandrov wrote:
> > On 17/03/2022 08:50, Mattias Forsblad wrote:
> > > This patch implements the bridge flood flags. There are three different
> > > flags matching unicast, multicast and broadcast. When the corresponding
> > > flag is cleared packets received on bridge ports will not be flooded
> > > towards the bridge.
> > > This makes is possible to only forward selected traffic between the
> > > port members of the bridge.
> > > 
> > > Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> > > ---
> > >  include/linux/if_bridge.h      |  6 +++++
> > >  include/uapi/linux/if_bridge.h |  9 ++++++-
> > >  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
> > >  net/bridge/br_device.c         |  3 +++
> > >  net/bridge/br_input.c          | 23 ++++++++++++++---
> > >  net/bridge/br_private.h        |  4 +++
> > >  6 files changed, 85 insertions(+), 5 deletions(-)
> > > 
> > Please always CC bridge maintainers for bridge patches.
> > I almost missed this one. I'll add my reply to Joachim's notes
> > which are pretty spot on.
> 
> And DSA maintainers for DSA patches ;) I was aimlessly scrolling through
> patchwork when I happened to notice these, and the series is already at v3.
> 
> As a matter of fact, I downloaded these patches from the mailing list
> with the intention of giving them a spin on mv88e6xxx to see what
> they're about, and to my surprise, this particular patch (I haven't even
> reached the offloading part) breaks DHCP on my bridge, so it can no
> longer get an IP address. I haven't toggled any bridge flag through
> netlink, just booted the board with systemd-networkd. The same thing
> happens with my LS1028A board. Further investigation to come, but this
> isn't off to a good start, I'm afraid...
> 
> > 
> > > diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
> > > index 3aae023a9353..fa8e000a6fb9 100644
> > > --- a/include/linux/if_bridge.h
> > > +++ b/include/linux/if_bridge.h
> > > @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
> > >  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
> > >  				    const unsigned char *addr,
> > >  				    __u16 vid);
> > > +bool br_flood_enabled(const struct net_device *dev);
> > >  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
> > >  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
> > >  u8 br_port_get_stp_state(const struct net_device *dev);
> > > @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
> > >  	return NULL;
> > >  }
> > >  
> > > +static inline bool br_flood_enabled(const struct net_device *dev)
> > > +{
> > > +	return true;
> > > +}
> > > +
> > >  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
> > >  {
> > >  }
> > > diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
> > > index 2711c3522010..765ed70c9b28 100644
> > > --- a/include/uapi/linux/if_bridge.h
> > > +++ b/include/uapi/linux/if_bridge.h
> > > @@ -72,6 +72,7 @@ struct __bridge_info {
> > >  	__u32 tcn_timer_value;
> > >  	__u32 topology_change_timer_value;
> > >  	__u32 gc_timer_value;
> > > +	__u8 flood;
> > >  };
> > >  
> > >  struct __port_info {
> > > @@ -752,13 +753,19 @@ 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
> > > + * BR_BOOLOPT_FLOOD - control bridge flood flag
> > > + * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
> > > + * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
> > >   *
> > >   * IMPORTANT: if adding a new option do not forget to handle
> > > - *            it in br_boolopt_toggle/get and bridge sysfs
> > > + *            it in br_boolopt_toggle/get
> > >   */
> > >  enum br_boolopt_id {
> > >  	BR_BOOLOPT_NO_LL_LEARN,
> > >  	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
> > > +	BR_BOOLOPT_FLOOD,
> > > +	BR_BOOLOPT_MCAST_FLOOD,
> > > +	BR_BOOLOPT_BCAST_FLOOD,
> > >  	BR_BOOLOPT_MAX
> > >  };
> > >  
> > > diff --git a/net/bridge/br.c b/net/bridge/br.c
> > > index b1dea3febeea..63a17bed6c63 100644
> > > --- a/net/bridge/br.c
> > > +++ b/net/bridge/br.c
> > > @@ -265,6 +265,11 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
> > >  	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
> > >  		err = br_multicast_toggle_vlan_snooping(br, on, extack);
> > >  		break;
> > > +	case BR_BOOLOPT_FLOOD:
> > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > +		err = br_flood_toggle(br, opt, on);
> > > +		break;
> > >  	default:
> > >  		/* shouldn't be called with unsupported options */
> > >  		WARN_ON(1);
> > > @@ -281,6 +286,12 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
> > >  		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);
> > > +	case BR_BOOLOPT_FLOOD:
> > > +		return br_opt_get(br, BROPT_FLOOD);
> > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > +		return br_opt_get(br, BROPT_MCAST_FLOOD);
> > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > +		return br_opt_get(br, BROPT_BCAST_FLOOD);
> > >  	default:
> > >  		/* shouldn't be called with unsupported options */
> > >  		WARN_ON(1);
> > > @@ -325,6 +336,40 @@ void br_boolopt_multi_get(const struct net_bridge *br,
> > >  	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
> > >  }
> > >  
> > > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
> > > +		    bool on)
> > > +{
> > > +	struct switchdev_attr attr = {
> > > +		.orig_dev = br->dev,
> > > +		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
> > > +		.flags = SWITCHDEV_F_DEFER,
> > > +	};
> > > +	enum net_bridge_opts bropt;
> > > +	int ret;
> > > +
> > > +	switch (opt) {
> > > +	case BR_BOOLOPT_FLOOD:
> > > +		bropt = BROPT_FLOOD;
> > > +		break;
> > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > +		bropt = BROPT_MCAST_FLOOD;
> > > +		break;
> > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > +		bropt = BROPT_BCAST_FLOOD;
> > > +		break;
> > > +	default:
> > > +		WARN_ON(1);
> > > +		return -EINVAL;
> > > +	}
> > > +	br_opt_toggle(br, bropt, on);
> > > +
> > > +	attr.u.brport_flags.mask = BIT(bropt);
> > > +	attr.u.brport_flags.val = on << bropt;
> > > +	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
> > > +
> > > +	return ret;
> > > +}
> > > +
> > >  /* private bridge options, controlled by the kernel */
> > >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
> > >  {
> > > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > > index 8d6bab244c4a..fafaef9d4b3a 100644
> > > --- a/net/bridge/br_device.c
> > > +++ b/net/bridge/br_device.c
> > > @@ -524,6 +524,9 @@ void br_dev_setup(struct net_device *dev)
> > >  	br->bridge_hello_time = br->hello_time = 2 * HZ;
> > >  	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
> > >  	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
> > > +	br_opt_toggle(br, BROPT_FLOOD, true);
> > > +	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
> > > +	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
> > >  	dev->max_mtu = ETH_MAX_MTU;
> > >  
> > >  	br_netfilter_rtable_init(br);
> > > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > > index e0c13fcc50ed..fcb0757bfdcc 100644
> > > --- a/net/bridge/br_input.c
> > > +++ b/net/bridge/br_input.c
> > > @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > >  		/* by definition the broadcast is also a multicast address */
> > >  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
> > >  			pkt_type = BR_PKT_BROADCAST;
> > > -			local_rcv = true;
> > > +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> > >  		} else {
> > >  			pkt_type = BR_PKT_MULTICAST;
> > > -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > > -				goto drop;
> > > +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> > > +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > > +					goto drop;
> > >  		}
> > >  	}
> > >  
> > > @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > >  			local_rcv = true;
> > >  			br->dev->stats.multicast++;
> > >  		}
> > > +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
> > > +			local_rcv = false;
> > >  		break;
> > >  	case BR_PKT_UNICAST:
> > >  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
> > > +		if (!br_opt_get(br, BROPT_FLOOD))
> > > +			local_rcv = false;
> > >  		break;
> > >  	default:
> > >  		break;
> > > @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > >  	if (dst) {
> > >  		unsigned long now = jiffies;
> > >  
> > > -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
> > > +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)

So this is the line that breaks local termination. Could you explain
the reasoning for this change? For unicast packets matching a local FDB
entry, local_rcv used to be irrelevant (and wasn't even set to true,
unless the bridge device was promiscuous).

> > >  			return br_pass_frame_up(skb);
> > >  
> > >  		if (now != dst->used)
> > > @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > >  }
> > >  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
> > >  
> > > +bool br_flood_enabled(const struct net_device *dev)
> > > +{
> > > +	struct net_bridge *br = netdev_priv(dev);
> > > +
> > > +	return !!(br_opt_get(br, BROPT_FLOOD) ||
> > > +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
> > > +		   br_opt_get(br, BROPT_BCAST_FLOOD));
> > > +}
> > > +EXPORT_SYMBOL_GPL(br_flood_enabled);
> > > +
> > >  static void __br_handle_local_finish(struct sk_buff *skb)
> > >  {
> > >  	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
> > > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > > index 48bc61ebc211..cf88dce0b92b 100644
> > > --- a/net/bridge/br_private.h
> > > +++ b/net/bridge/br_private.h
> > > @@ -445,6 +445,9 @@ enum net_bridge_opts {
> > >  	BROPT_NO_LL_LEARN,
> > >  	BROPT_VLAN_BRIDGE_BINDING,
> > >  	BROPT_MCAST_VLAN_SNOOPING_ENABLED,
> > > +	BROPT_FLOOD,
> > > +	BROPT_MCAST_FLOOD,
> > > +	BROPT_BCAST_FLOOD,
> > >  };
> > >  
> > >  struct net_bridge {
> > > @@ -720,6 +723,7 @@ int br_boolopt_multi_toggle(struct net_bridge *br,
> > >  void br_boolopt_multi_get(const struct net_bridge *br,
> > >  			  struct br_boolopt_multi *bm);
> > >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on);
> > > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on);
> > >  
> > >  /* br_device.c */
> > >  void br_dev_setup(struct net_device *dev);
> >
Vladimir Oltean March 17, 2022, 11:59 a.m. UTC | #9
On Thu, Mar 17, 2022 at 01:57:03PM +0200, Vladimir Oltean wrote:
> On Thu, Mar 17, 2022 at 01:15:45PM +0200, Vladimir Oltean wrote:
> > On Thu, Mar 17, 2022 at 12:11:40PM +0200, Nikolay Aleksandrov wrote:
> > > On 17/03/2022 08:50, Mattias Forsblad wrote:
> > > > This patch implements the bridge flood flags. There are three different
> > > > flags matching unicast, multicast and broadcast. When the corresponding
> > > > flag is cleared packets received on bridge ports will not be flooded
> > > > towards the bridge.
> > > > This makes is possible to only forward selected traffic between the
> > > > port members of the bridge.
> > > > 
> > > > Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
> > > > ---
> > > >  include/linux/if_bridge.h      |  6 +++++
> > > >  include/uapi/linux/if_bridge.h |  9 ++++++-
> > > >  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
> > > >  net/bridge/br_device.c         |  3 +++
> > > >  net/bridge/br_input.c          | 23 ++++++++++++++---
> > > >  net/bridge/br_private.h        |  4 +++
> > > >  6 files changed, 85 insertions(+), 5 deletions(-)
> > > > 
> > > Please always CC bridge maintainers for bridge patches.
> > > I almost missed this one. I'll add my reply to Joachim's notes
> > > which are pretty spot on.
> > 
> > And DSA maintainers for DSA patches ;) I was aimlessly scrolling through
> > patchwork when I happened to notice these, and the series is already at v3.
> > 
> > As a matter of fact, I downloaded these patches from the mailing list
> > with the intention of giving them a spin on mv88e6xxx to see what
> > they're about, and to my surprise, this particular patch (I haven't even
> > reached the offloading part) breaks DHCP on my bridge, so it can no
> > longer get an IP address. I haven't toggled any bridge flag through
> > netlink, just booted the board with systemd-networkd. The same thing
> > happens with my LS1028A board. Further investigation to come, but this
> > isn't off to a good start, I'm afraid...
> > 
> > > 
> > > > diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
> > > > index 3aae023a9353..fa8e000a6fb9 100644
> > > > --- a/include/linux/if_bridge.h
> > > > +++ b/include/linux/if_bridge.h
> > > > @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
> > > >  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
> > > >  				    const unsigned char *addr,
> > > >  				    __u16 vid);
> > > > +bool br_flood_enabled(const struct net_device *dev);
> > > >  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
> > > >  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
> > > >  u8 br_port_get_stp_state(const struct net_device *dev);
> > > > @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
> > > >  	return NULL;
> > > >  }
> > > >  
> > > > +static inline bool br_flood_enabled(const struct net_device *dev)
> > > > +{
> > > > +	return true;
> > > > +}
> > > > +
> > > >  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
> > > >  {
> > > >  }
> > > > diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
> > > > index 2711c3522010..765ed70c9b28 100644
> > > > --- a/include/uapi/linux/if_bridge.h
> > > > +++ b/include/uapi/linux/if_bridge.h
> > > > @@ -72,6 +72,7 @@ struct __bridge_info {
> > > >  	__u32 tcn_timer_value;
> > > >  	__u32 topology_change_timer_value;
> > > >  	__u32 gc_timer_value;
> > > > +	__u8 flood;
> > > >  };
> > > >  
> > > >  struct __port_info {
> > > > @@ -752,13 +753,19 @@ 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
> > > > + * BR_BOOLOPT_FLOOD - control bridge flood flag
> > > > + * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
> > > > + * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
> > > >   *
> > > >   * IMPORTANT: if adding a new option do not forget to handle
> > > > - *            it in br_boolopt_toggle/get and bridge sysfs
> > > > + *            it in br_boolopt_toggle/get
> > > >   */
> > > >  enum br_boolopt_id {
> > > >  	BR_BOOLOPT_NO_LL_LEARN,
> > > >  	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
> > > > +	BR_BOOLOPT_FLOOD,
> > > > +	BR_BOOLOPT_MCAST_FLOOD,
> > > > +	BR_BOOLOPT_BCAST_FLOOD,
> > > >  	BR_BOOLOPT_MAX
> > > >  };
> > > >  
> > > > diff --git a/net/bridge/br.c b/net/bridge/br.c
> > > > index b1dea3febeea..63a17bed6c63 100644
> > > > --- a/net/bridge/br.c
> > > > +++ b/net/bridge/br.c
> > > > @@ -265,6 +265,11 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
> > > >  	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
> > > >  		err = br_multicast_toggle_vlan_snooping(br, on, extack);
> > > >  		break;
> > > > +	case BR_BOOLOPT_FLOOD:
> > > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > > +		err = br_flood_toggle(br, opt, on);
> > > > +		break;
> > > >  	default:
> > > >  		/* shouldn't be called with unsupported options */
> > > >  		WARN_ON(1);
> > > > @@ -281,6 +286,12 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
> > > >  		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);
> > > > +	case BR_BOOLOPT_FLOOD:
> > > > +		return br_opt_get(br, BROPT_FLOOD);
> > > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > > +		return br_opt_get(br, BROPT_MCAST_FLOOD);
> > > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > > +		return br_opt_get(br, BROPT_BCAST_FLOOD);
> > > >  	default:
> > > >  		/* shouldn't be called with unsupported options */
> > > >  		WARN_ON(1);
> > > > @@ -325,6 +336,40 @@ void br_boolopt_multi_get(const struct net_bridge *br,
> > > >  	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
> > > >  }
> > > >  
> > > > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
> > > > +		    bool on)
> > > > +{
> > > > +	struct switchdev_attr attr = {
> > > > +		.orig_dev = br->dev,
> > > > +		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
> > > > +		.flags = SWITCHDEV_F_DEFER,
> > > > +	};
> > > > +	enum net_bridge_opts bropt;
> > > > +	int ret;
> > > > +
> > > > +	switch (opt) {
> > > > +	case BR_BOOLOPT_FLOOD:
> > > > +		bropt = BROPT_FLOOD;
> > > > +		break;
> > > > +	case BR_BOOLOPT_MCAST_FLOOD:
> > > > +		bropt = BROPT_MCAST_FLOOD;
> > > > +		break;
> > > > +	case BR_BOOLOPT_BCAST_FLOOD:
> > > > +		bropt = BROPT_BCAST_FLOOD;
> > > > +		break;
> > > > +	default:
> > > > +		WARN_ON(1);
> > > > +		return -EINVAL;
> > > > +	}
> > > > +	br_opt_toggle(br, bropt, on);
> > > > +
> > > > +	attr.u.brport_flags.mask = BIT(bropt);
> > > > +	attr.u.brport_flags.val = on << bropt;
> > > > +	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
> > > > +
> > > > +	return ret;
> > > > +}
> > > > +
> > > >  /* private bridge options, controlled by the kernel */
> > > >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
> > > >  {
> > > > diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
> > > > index 8d6bab244c4a..fafaef9d4b3a 100644
> > > > --- a/net/bridge/br_device.c
> > > > +++ b/net/bridge/br_device.c
> > > > @@ -524,6 +524,9 @@ void br_dev_setup(struct net_device *dev)
> > > >  	br->bridge_hello_time = br->hello_time = 2 * HZ;
> > > >  	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
> > > >  	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
> > > > +	br_opt_toggle(br, BROPT_FLOOD, true);
> > > > +	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
> > > > +	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
> > > >  	dev->max_mtu = ETH_MAX_MTU;
> > > >  
> > > >  	br_netfilter_rtable_init(br);
> > > > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> > > > index e0c13fcc50ed..fcb0757bfdcc 100644
> > > > --- a/net/bridge/br_input.c
> > > > +++ b/net/bridge/br_input.c
> > > > @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > > >  		/* by definition the broadcast is also a multicast address */
> > > >  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
> > > >  			pkt_type = BR_PKT_BROADCAST;
> > > > -			local_rcv = true;
> > > > +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> > > >  		} else {
> > > >  			pkt_type = BR_PKT_MULTICAST;
> > > > -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > > > -				goto drop;
> > > > +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> > > > +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> > > > +					goto drop;
> > > >  		}
> > > >  	}
> > > >  
> > > > @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > > >  			local_rcv = true;
> > > >  			br->dev->stats.multicast++;
> > > >  		}
> > > > +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
> > > > +			local_rcv = false;
> > > >  		break;
> > > >  	case BR_PKT_UNICAST:
> > > >  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
> > > > +		if (!br_opt_get(br, BROPT_FLOOD))
> > > > +			local_rcv = false;
> > > >  		break;
> > > >  	default:
> > > >  		break;
> > > > @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > > >  	if (dst) {
> > > >  		unsigned long now = jiffies;
> > > >  
> > > > -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
> > > > +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
> 
> So this is the line that breaks local termination. Could you explain
> the reasoning for this change? For unicast packets matching a local FDB
> entry, local_rcv used to be irrelevant (and wasn't even set to true,
> unless the bridge device was promiscuous).

Sorry, it wasn't obvious from the To: field, but the question was
targeted to Mattias and not to Nikolay.

> > > >  			return br_pass_frame_up(skb);
> > > >  
> > > >  		if (now != dst->used)
> > > > @@ -190,6 +195,16 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> > > >  }
> > > >  EXPORT_SYMBOL_GPL(br_handle_frame_finish);
> > > >  
> > > > +bool br_flood_enabled(const struct net_device *dev)
> > > > +{
> > > > +	struct net_bridge *br = netdev_priv(dev);
> > > > +
> > > > +	return !!(br_opt_get(br, BROPT_FLOOD) ||
> > > > +		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
> > > > +		   br_opt_get(br, BROPT_BCAST_FLOOD));
> > > > +}
> > > > +EXPORT_SYMBOL_GPL(br_flood_enabled);
> > > > +
> > > >  static void __br_handle_local_finish(struct sk_buff *skb)
> > > >  {
> > > >  	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
> > > > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
> > > > index 48bc61ebc211..cf88dce0b92b 100644
> > > > --- a/net/bridge/br_private.h
> > > > +++ b/net/bridge/br_private.h
> > > > @@ -445,6 +445,9 @@ enum net_bridge_opts {
> > > >  	BROPT_NO_LL_LEARN,
> > > >  	BROPT_VLAN_BRIDGE_BINDING,
> > > >  	BROPT_MCAST_VLAN_SNOOPING_ENABLED,
> > > > +	BROPT_FLOOD,
> > > > +	BROPT_MCAST_FLOOD,
> > > > +	BROPT_BCAST_FLOOD,
> > > >  };
> > > >  
> > > >  struct net_bridge {
> > > > @@ -720,6 +723,7 @@ int br_boolopt_multi_toggle(struct net_bridge *br,
> > > >  void br_boolopt_multi_get(const struct net_bridge *br,
> > > >  			  struct br_boolopt_multi *bm);
> > > >  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on);
> > > > +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on);
> > > >  
> > > >  /* br_device.c */
> > > >  void br_dev_setup(struct net_device *dev);
> > >
Mattias Forsblad March 17, 2022, 12:08 p.m. UTC | #10
On 2022-03-17 12:59, Vladimir Oltean wrote:
> On Thu, Mar 17, 2022 at 01:57:03PM +0200, Vladimir Oltean wrote:
>> On Thu, Mar 17, 2022 at 01:15:45PM +0200, Vladimir Oltean wrote:
>>> On Thu, Mar 17, 2022 at 12:11:40PM +0200, Nikolay Aleksandrov wrote:
>>>> On 17/03/2022 08:50, Mattias Forsblad wrote:
>>>>> This patch implements the bridge flood flags. There are three different
>>>>> flags matching unicast, multicast and broadcast. When the corresponding
>>>>> flag is cleared packets received on bridge ports will not be flooded
>>>>> towards the bridge.
>>>>> This makes is possible to only forward selected traffic between the
>>>>> port members of the bridge.
>>>>>
>>>>> Signed-off-by: Mattias Forsblad <mattias.forsblad@gmail.com>
>>>>> ---
>>>>>  include/linux/if_bridge.h      |  6 +++++
>>>>>  include/uapi/linux/if_bridge.h |  9 ++++++-
>>>>>  net/bridge/br.c                | 45 ++++++++++++++++++++++++++++++++++
>>>>>  net/bridge/br_device.c         |  3 +++
>>>>>  net/bridge/br_input.c          | 23 ++++++++++++++---
>>>>>  net/bridge/br_private.h        |  4 +++
>>>>>  6 files changed, 85 insertions(+), 5 deletions(-)
>>>>>
>>>> Please always CC bridge maintainers for bridge patches.
>>>> I almost missed this one. I'll add my reply to Joachim's notes
>>>> which are pretty spot on.
>>>
>>> And DSA maintainers for DSA patches ;) I was aimlessly scrolling through
>>> patchwork when I happened to notice these, and the series is already at v3.
>>>
>>> As a matter of fact, I downloaded these patches from the mailing list
>>> with the intention of giving them a spin on mv88e6xxx to see what
>>> they're about, and to my surprise, this particular patch (I haven't even
>>> reached the offloading part) breaks DHCP on my bridge, so it can no
>>> longer get an IP address. I haven't toggled any bridge flag through
>>> netlink, just booted the board with systemd-networkd. The same thing
>>> happens with my LS1028A board. Further investigation to come, but this
>>> isn't off to a good start, I'm afraid...
>>>
>>>>
>>>>> diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
>>>>> index 3aae023a9353..fa8e000a6fb9 100644
>>>>> --- a/include/linux/if_bridge.h
>>>>> +++ b/include/linux/if_bridge.h
>>>>> @@ -157,6 +157,7 @@ static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
>>>>>  struct net_device *br_fdb_find_port(const struct net_device *br_dev,
>>>>>  				    const unsigned char *addr,
>>>>>  				    __u16 vid);
>>>>> +bool br_flood_enabled(const struct net_device *dev);
>>>>>  void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
>>>>>  bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
>>>>>  u8 br_port_get_stp_state(const struct net_device *dev);
>>>>> @@ -170,6 +171,11 @@ br_fdb_find_port(const struct net_device *br_dev,
>>>>>  	return NULL;
>>>>>  }
>>>>>  
>>>>> +static inline bool br_flood_enabled(const struct net_device *dev)
>>>>> +{
>>>>> +	return true;
>>>>> +}
>>>>> +
>>>>>  static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
>>>>>  {
>>>>>  }
>>>>> diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
>>>>> index 2711c3522010..765ed70c9b28 100644
>>>>> --- a/include/uapi/linux/if_bridge.h
>>>>> +++ b/include/uapi/linux/if_bridge.h
>>>>> @@ -72,6 +72,7 @@ struct __bridge_info {
>>>>>  	__u32 tcn_timer_value;
>>>>>  	__u32 topology_change_timer_value;
>>>>>  	__u32 gc_timer_value;
>>>>> +	__u8 flood;
>>>>>  };
>>>>>  
>>>>>  struct __port_info {
>>>>> @@ -752,13 +753,19 @@ 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
>>>>> + * BR_BOOLOPT_FLOOD - control bridge flood flag
>>>>> + * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
>>>>> + * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
>>>>>   *
>>>>>   * IMPORTANT: if adding a new option do not forget to handle
>>>>> - *            it in br_boolopt_toggle/get and bridge sysfs
>>>>> + *            it in br_boolopt_toggle/get
>>>>>   */
>>>>>  enum br_boolopt_id {
>>>>>  	BR_BOOLOPT_NO_LL_LEARN,
>>>>>  	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
>>>>> +	BR_BOOLOPT_FLOOD,
>>>>> +	BR_BOOLOPT_MCAST_FLOOD,
>>>>> +	BR_BOOLOPT_BCAST_FLOOD,
>>>>>  	BR_BOOLOPT_MAX
>>>>>  };
>>>>>  
>>>>> diff --git a/net/bridge/br.c b/net/bridge/br.c
>>>>> index b1dea3febeea..63a17bed6c63 100644
>>>>> --- a/net/bridge/br.c
>>>>> +++ b/net/bridge/br.c
>>>>> @@ -265,6 +265,11 @@ int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
>>>>>  	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
>>>>>  		err = br_multicast_toggle_vlan_snooping(br, on, extack);
>>>>>  		break;
>>>>> +	case BR_BOOLOPT_FLOOD:
>>>>> +	case BR_BOOLOPT_MCAST_FLOOD:
>>>>> +	case BR_BOOLOPT_BCAST_FLOOD:
>>>>> +		err = br_flood_toggle(br, opt, on);
>>>>> +		break;
>>>>>  	default:
>>>>>  		/* shouldn't be called with unsupported options */
>>>>>  		WARN_ON(1);
>>>>> @@ -281,6 +286,12 @@ int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
>>>>>  		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);
>>>>> +	case BR_BOOLOPT_FLOOD:
>>>>> +		return br_opt_get(br, BROPT_FLOOD);
>>>>> +	case BR_BOOLOPT_MCAST_FLOOD:
>>>>> +		return br_opt_get(br, BROPT_MCAST_FLOOD);
>>>>> +	case BR_BOOLOPT_BCAST_FLOOD:
>>>>> +		return br_opt_get(br, BROPT_BCAST_FLOOD);
>>>>>  	default:
>>>>>  		/* shouldn't be called with unsupported options */
>>>>>  		WARN_ON(1);
>>>>> @@ -325,6 +336,40 @@ void br_boolopt_multi_get(const struct net_bridge *br,
>>>>>  	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
>>>>>  }
>>>>>  
>>>>> +int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
>>>>> +		    bool on)
>>>>> +{
>>>>> +	struct switchdev_attr attr = {
>>>>> +		.orig_dev = br->dev,
>>>>> +		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
>>>>> +		.flags = SWITCHDEV_F_DEFER,
>>>>> +	};
>>>>> +	enum net_bridge_opts bropt;
>>>>> +	int ret;
>>>>> +
>>>>> +	switch (opt) {
>>>>> +	case BR_BOOLOPT_FLOOD:
>>>>> +		bropt = BROPT_FLOOD;
>>>>> +		break;
>>>>> +	case BR_BOOLOPT_MCAST_FLOOD:
>>>>> +		bropt = BROPT_MCAST_FLOOD;
>>>>> +		break;
>>>>> +	case BR_BOOLOPT_BCAST_FLOOD:
>>>>> +		bropt = BROPT_BCAST_FLOOD;
>>>>> +		break;
>>>>> +	default:
>>>>> +		WARN_ON(1);
>>>>> +		return -EINVAL;
>>>>> +	}
>>>>> +	br_opt_toggle(br, bropt, on);
>>>>> +
>>>>> +	attr.u.brport_flags.mask = BIT(bropt);
>>>>> +	attr.u.brport_flags.val = on << bropt;
>>>>> +	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>>  /* private bridge options, controlled by the kernel */
>>>>>  void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
>>>>>  {
>>>>> diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
>>>>> index 8d6bab244c4a..fafaef9d4b3a 100644
>>>>> --- a/net/bridge/br_device.c
>>>>> +++ b/net/bridge/br_device.c
>>>>> @@ -524,6 +524,9 @@ void br_dev_setup(struct net_device *dev)
>>>>>  	br->bridge_hello_time = br->hello_time = 2 * HZ;
>>>>>  	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
>>>>>  	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
>>>>> +	br_opt_toggle(br, BROPT_FLOOD, true);
>>>>> +	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
>>>>> +	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
>>>>>  	dev->max_mtu = ETH_MAX_MTU;
>>>>>  
>>>>>  	br_netfilter_rtable_init(br);
>>>>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>>>>> index e0c13fcc50ed..fcb0757bfdcc 100644
>>>>> --- a/net/bridge/br_input.c
>>>>> +++ b/net/bridge/br_input.c
>>>>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>>>>  		/* by definition the broadcast is also a multicast address */
>>>>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>>>>>  			pkt_type = BR_PKT_BROADCAST;
>>>>> -			local_rcv = true;
>>>>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
>>>>>  		} else {
>>>>>  			pkt_type = BR_PKT_MULTICAST;
>>>>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>>>>> -				goto drop;
>>>>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>>>>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>>>>> +					goto drop;
>>>>>  		}
>>>>>  	}
>>>>>  
>>>>> @@ -155,9 +156,13 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>>>>  			local_rcv = true;
>>>>>  			br->dev->stats.multicast++;
>>>>>  		}
>>>>> +		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
>>>>> +			local_rcv = false;
>>>>>  		break;
>>>>>  	case BR_PKT_UNICAST:
>>>>>  		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
>>>>> +		if (!br_opt_get(br, BROPT_FLOOD))
>>>>> +			local_rcv = false;
>>>>>  		break;
>>>>>  	default:
>>>>>  		break;
>>>>> @@ -166,7 +171,7 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>>>>>  	if (dst) {
>>>>>  		unsigned long now = jiffies;
>>>>>  
>>>>> -		if (test_bit(BR_FDB_LOCAL, &dst->flags))
>>>>> +		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
>>
>> So this is the line that breaks local termination. Could you explain
>> the reasoning for this change? For unicast packets matching a local FDB
>> entry, local_rcv used to be irrelevant (and wasn't even set to true,
>> unless the bridge device was promiscuous).
> 
> Sorry, it wasn't obvious from the To: field, but the question was
> targeted to Mattias and not to Nikolay.
> 

It might as simple be that I've misunderstood that flow.

/Mattias
Ido Schimmel March 17, 2022, 12:26 p.m. UTC | #11
On Thu, Mar 17, 2022 at 01:42:55PM +0200, Nikolay Aleksandrov wrote:
> On 17/03/2022 13:39, Mattias Forsblad wrote:
> > On 2022-03-17 10:07, Joachim Wiberg wrote:
> >> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
> >>> This patch implements the bridge flood flags. There are three different
> >>> flags matching unicast, multicast and broadcast. When the corresponding
> >>> flag is cleared packets received on bridge ports will not be flooded
> >>> towards the bridge.
> >>
> >> If I've not completely misunderstood things, I believe the flood and
> >> mcast_flood flags operate on unknown unicast and multicast.  With that
> >> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
> >> add my own comments below.
> >>
> >> Happy incident I saw this patch set, I have a very similar one for these
> >> flags to the bridge itself, with the intent to improve handling of all
> >> classes of multicast to/from the bridge itself.
> >>
> >>> [snip]
> >>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> >>> index e0c13fcc50ed..fcb0757bfdcc 100644
> >>> --- a/net/bridge/br_input.c
> >>> +++ b/net/bridge/br_input.c
> >>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >>>  		/* by definition the broadcast is also a multicast address */
> >>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
> >>>  			pkt_type = BR_PKT_BROADCAST;
> >>> -			local_rcv = true;
> >>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> >>
> >> Minor comment, I believe the preferred style is more like this:
> >>
> >> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
> >>         	local_rcv = true;
> >>
> >>>  		} else {
> >>>  			pkt_type = BR_PKT_MULTICAST;
> >>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> >>> -				goto drop;
> >>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> >>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> >>> +					goto drop;
> >>
> >> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
> >> we cannot bypass the call to br_multicast_rcv(), which helps with the
> >> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
> >> router ports, while the mdb lookup (below) is what an tell us if we
> >> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
> >> flag for the bridge itself.
> > 
> > The original flag was name was local_receive to separate it from being
> > mistaken for the flood unknown flags. However the comment I've got was
> > to align it with the existing (port) flags. These flags have nothing to do with
> > the port flood unknown flags. Imagine the setup below:
> > 
> >            vlan1
> >              |
> >             br0             br1
> >            /   \           /   \
> >          swp1 swp2       swp3 swp4
> > 
> > We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
> > On br1 we want to just forward packets between swp3/4 and disable learning. 
> > Additional we don't want this traffic to impact the CPU. 
> > If we disable learning on swp3/4 all traffic will be unknown and if we also 
> > have flood unknown on the CPU-port because of requirements for br0 it will
> > impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
> > with the help of the PVT.
> > 
> > /Mattias
> 
> The feedback was correct and we all assumed unknown traffic control.
> If you don't want any local receive then use filtering rules. Don't add unnecessary flags.

Yep. Very easy with tc:

# tc qdisc add dev br1 clsact
# tc filter add dev br1 ingress pref 1 proto all matchall action drop

This can be fully implemented inside the relevant device driver, no
changes needed in the bridge driver.
Tobias Waldekranz March 17, 2022, 1:34 p.m. UTC | #12
On Thu, Mar 17, 2022 at 14:26, Ido Schimmel <idosch@idosch.org> wrote:
> On Thu, Mar 17, 2022 at 01:42:55PM +0200, Nikolay Aleksandrov wrote:
>> On 17/03/2022 13:39, Mattias Forsblad wrote:
>> > On 2022-03-17 10:07, Joachim Wiberg wrote:
>> >> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
>> >>> This patch implements the bridge flood flags. There are three different
>> >>> flags matching unicast, multicast and broadcast. When the corresponding
>> >>> flag is cleared packets received on bridge ports will not be flooded
>> >>> towards the bridge.
>> >>
>> >> If I've not completely misunderstood things, I believe the flood and
>> >> mcast_flood flags operate on unknown unicast and multicast.  With that
>> >> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
>> >> add my own comments below.
>> >>
>> >> Happy incident I saw this patch set, I have a very similar one for these
>> >> flags to the bridge itself, with the intent to improve handling of all
>> >> classes of multicast to/from the bridge itself.
>> >>
>> >>> [snip]
>> >>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>> >>> index e0c13fcc50ed..fcb0757bfdcc 100644
>> >>> --- a/net/bridge/br_input.c
>> >>> +++ b/net/bridge/br_input.c
>> >>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>> >>>  		/* by definition the broadcast is also a multicast address */
>> >>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>> >>>  			pkt_type = BR_PKT_BROADCAST;
>> >>> -			local_rcv = true;
>> >>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
>> >>
>> >> Minor comment, I believe the preferred style is more like this:
>> >>
>> >> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
>> >>         	local_rcv = true;
>> >>
>> >>>  		} else {
>> >>>  			pkt_type = BR_PKT_MULTICAST;
>> >>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> >>> -				goto drop;
>> >>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>> >>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> >>> +					goto drop;
>> >>
>> >> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
>> >> we cannot bypass the call to br_multicast_rcv(), which helps with the
>> >> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
>> >> router ports, while the mdb lookup (below) is what an tell us if we
>> >> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
>> >> flag for the bridge itself.
>> > 
>> > The original flag was name was local_receive to separate it from being
>> > mistaken for the flood unknown flags. However the comment I've got was
>> > to align it with the existing (port) flags. These flags have nothing to do with
>> > the port flood unknown flags. Imagine the setup below:
>> > 
>> >            vlan1
>> >              |
>> >             br0             br1
>> >            /   \           /   \
>> >          swp1 swp2       swp3 swp4
>> > 
>> > We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
>> > On br1 we want to just forward packets between swp3/4 and disable learning. 
>> > Additional we don't want this traffic to impact the CPU. 
>> > If we disable learning on swp3/4 all traffic will be unknown and if we also 
>> > have flood unknown on the CPU-port because of requirements for br0 it will
>> > impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
>> > with the help of the PVT.
>> > 
>> > /Mattias
>> 
>> The feedback was correct and we all assumed unknown traffic control.
>> If you don't want any local receive then use filtering rules. Don't add unnecessary flags.
>
> Yep. Very easy with tc:
>
> # tc qdisc add dev br1 clsact
> # tc filter add dev br1 ingress pref 1 proto all matchall action drop
>
> This can be fully implemented inside the relevant device driver, no
> changes needed in the bridge driver.

Interesting. Are you saying that a switchdev driver can offload rules
for a netdev that it does not directly control (e.g. bridge that it is
connected to)? It thought that you had to bind the relevant ports to the
same block to approximate that. If so, are any drivers implementing
this? I did a quick scan of mlxsw, but could not find anything obvious.
Ido Schimmel March 17, 2022, 2:10 p.m. UTC | #13
On Thu, Mar 17, 2022 at 02:34:38PM +0100, Tobias Waldekranz wrote:
> On Thu, Mar 17, 2022 at 14:26, Ido Schimmel <idosch@idosch.org> wrote:
> > On Thu, Mar 17, 2022 at 01:42:55PM +0200, Nikolay Aleksandrov wrote:
> >> On 17/03/2022 13:39, Mattias Forsblad wrote:
> >> > On 2022-03-17 10:07, Joachim Wiberg wrote:
> >> >> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
> >> >>> This patch implements the bridge flood flags. There are three different
> >> >>> flags matching unicast, multicast and broadcast. When the corresponding
> >> >>> flag is cleared packets received on bridge ports will not be flooded
> >> >>> towards the bridge.
> >> >>
> >> >> If I've not completely misunderstood things, I believe the flood and
> >> >> mcast_flood flags operate on unknown unicast and multicast.  With that
> >> >> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
> >> >> add my own comments below.
> >> >>
> >> >> Happy incident I saw this patch set, I have a very similar one for these
> >> >> flags to the bridge itself, with the intent to improve handling of all
> >> >> classes of multicast to/from the bridge itself.
> >> >>
> >> >>> [snip]
> >> >>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
> >> >>> index e0c13fcc50ed..fcb0757bfdcc 100644
> >> >>> --- a/net/bridge/br_input.c
> >> >>> +++ b/net/bridge/br_input.c
> >> >>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
> >> >>>  		/* by definition the broadcast is also a multicast address */
> >> >>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
> >> >>>  			pkt_type = BR_PKT_BROADCAST;
> >> >>> -			local_rcv = true;
> >> >>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
> >> >>
> >> >> Minor comment, I believe the preferred style is more like this:
> >> >>
> >> >> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
> >> >>         	local_rcv = true;
> >> >>
> >> >>>  		} else {
> >> >>>  			pkt_type = BR_PKT_MULTICAST;
> >> >>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> >> >>> -				goto drop;
> >> >>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
> >> >>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
> >> >>> +					goto drop;
> >> >>
> >> >> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
> >> >> we cannot bypass the call to br_multicast_rcv(), which helps with the
> >> >> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
> >> >> router ports, while the mdb lookup (below) is what an tell us if we
> >> >> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
> >> >> flag for the bridge itself.
> >> > 
> >> > The original flag was name was local_receive to separate it from being
> >> > mistaken for the flood unknown flags. However the comment I've got was
> >> > to align it with the existing (port) flags. These flags have nothing to do with
> >> > the port flood unknown flags. Imagine the setup below:
> >> > 
> >> >            vlan1
> >> >              |
> >> >             br0             br1
> >> >            /   \           /   \
> >> >          swp1 swp2       swp3 swp4
> >> > 
> >> > We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
> >> > On br1 we want to just forward packets between swp3/4 and disable learning. 
> >> > Additional we don't want this traffic to impact the CPU. 
> >> > If we disable learning on swp3/4 all traffic will be unknown and if we also 
> >> > have flood unknown on the CPU-port because of requirements for br0 it will
> >> > impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
> >> > with the help of the PVT.
> >> > 
> >> > /Mattias
> >> 
> >> The feedback was correct and we all assumed unknown traffic control.
> >> If you don't want any local receive then use filtering rules. Don't add unnecessary flags.
> >
> > Yep. Very easy with tc:
> >
> > # tc qdisc add dev br1 clsact
> > # tc filter add dev br1 ingress pref 1 proto all matchall action drop
> >
> > This can be fully implemented inside the relevant device driver, no
> > changes needed in the bridge driver.
> 
> Interesting. Are you saying that a switchdev driver can offload rules
> for a netdev that it does not directly control (e.g. bridge that it is
> connected to)? It thought that you had to bind the relevant ports to the
> same block to approximate that. If so, are any drivers implementing
> this? I did a quick scan of mlxsw, but could not find anything obvious.

Yes, currently mlxsw only supports filters configured on physical
netdevs, but the HW can support more advanced configurations such as
filters on a bridge device (or a VLAN upper). These would be translated
to ACLs configured on the ingress/egress router interface (RIF) backing
the netdev. NIC drivers support much more advanced tc offloads due to
the prevalent usage of OVS in this space, so might be better to check
them instead.

TBH, I never looked too deeply into this, but assumed it's supported via
the indirect tc block infra. See commit 7f76fa36754b ("net: sched:
register callbacks for indirect tc block binds") for more details.

Even if I got it wrong, it would be beneficial to extend the tc offload
infra rather than patching the bridge driver to achieve a functionality
that is already supported in the SW data path via tc.
Tobias Waldekranz March 17, 2022, 4:02 p.m. UTC | #14
On Thu, Mar 17, 2022 at 16:10, Ido Schimmel <idosch@idosch.org> wrote:
> On Thu, Mar 17, 2022 at 02:34:38PM +0100, Tobias Waldekranz wrote:
>> On Thu, Mar 17, 2022 at 14:26, Ido Schimmel <idosch@idosch.org> wrote:
>> > On Thu, Mar 17, 2022 at 01:42:55PM +0200, Nikolay Aleksandrov wrote:
>> >> On 17/03/2022 13:39, Mattias Forsblad wrote:
>> >> > On 2022-03-17 10:07, Joachim Wiberg wrote:
>> >> >> On Thu, Mar 17, 2022 at 07:50, Mattias Forsblad <mattias.forsblad@gmail.com> wrote:
>> >> >>> This patch implements the bridge flood flags. There are three different
>> >> >>> flags matching unicast, multicast and broadcast. When the corresponding
>> >> >>> flag is cleared packets received on bridge ports will not be flooded
>> >> >>> towards the bridge.
>> >> >>
>> >> >> If I've not completely misunderstood things, I believe the flood and
>> >> >> mcast_flood flags operate on unknown unicast and multicast.  With that
>> >> >> in mind I think the hot path in br_input.c needs a bit more eyes.  I'll
>> >> >> add my own comments below.
>> >> >>
>> >> >> Happy incident I saw this patch set, I have a very similar one for these
>> >> >> flags to the bridge itself, with the intent to improve handling of all
>> >> >> classes of multicast to/from the bridge itself.
>> >> >>
>> >> >>> [snip]
>> >> >>> diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
>> >> >>> index e0c13fcc50ed..fcb0757bfdcc 100644
>> >> >>> --- a/net/bridge/br_input.c
>> >> >>> +++ b/net/bridge/br_input.c
>> >> >>> @@ -109,11 +109,12 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
>> >> >>>  		/* by definition the broadcast is also a multicast address */
>> >> >>>  		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
>> >> >>>  			pkt_type = BR_PKT_BROADCAST;
>> >> >>> -			local_rcv = true;
>> >> >>> +			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
>> >> >>
>> >> >> Minor comment, I believe the preferred style is more like this:
>> >> >>
>> >> >> 	if (br_opt_get(br, BROPT_BCAST_FLOOD))
>> >> >>         	local_rcv = true;
>> >> >>
>> >> >>>  		} else {
>> >> >>>  			pkt_type = BR_PKT_MULTICAST;
>> >> >>> -			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> >> >>> -				goto drop;
>> >> >>> +			if (br_opt_get(br, BROPT_MCAST_FLOOD))
>> >> >>> +				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
>> >> >>> +					goto drop;
>> >> >>
>> >> >> Since the BROPT_MCAST_FLOOD flag should only control uknown multicast,
>> >> >> we cannot bypass the call to br_multicast_rcv(), which helps with the
>> >> >> classifcation.  E.g., we want IGMP/MLD reports to be forwarded to all
>> >> >> router ports, while the mdb lookup (below) is what an tell us if we
>> >> >> have uknown multicast and there we can check the BROPT_MCAST_FLOOD
>> >> >> flag for the bridge itself.
>> >> > 
>> >> > The original flag was name was local_receive to separate it from being
>> >> > mistaken for the flood unknown flags. However the comment I've got was
>> >> > to align it with the existing (port) flags. These flags have nothing to do with
>> >> > the port flood unknown flags. Imagine the setup below:
>> >> > 
>> >> >            vlan1
>> >> >              |
>> >> >             br0             br1
>> >> >            /   \           /   \
>> >> >          swp1 swp2       swp3 swp4
>> >> > 
>> >> > We want to have swp1/2 as member of a normal vlan filtering bridge br0 /w learning on. 
>> >> > On br1 we want to just forward packets between swp3/4 and disable learning. 
>> >> > Additional we don't want this traffic to impact the CPU. 
>> >> > If we disable learning on swp3/4 all traffic will be unknown and if we also 
>> >> > have flood unknown on the CPU-port because of requirements for br0 it will
>> >> > impact the traffic to br1. Thus we want to restrict traffic between swp3/4<->CPU port
>> >> > with the help of the PVT.
>> >> > 
>> >> > /Mattias
>> >> 
>> >> The feedback was correct and we all assumed unknown traffic control.
>> >> If you don't want any local receive then use filtering rules. Don't add unnecessary flags.
>> >
>> > Yep. Very easy with tc:
>> >
>> > # tc qdisc add dev br1 clsact
>> > # tc filter add dev br1 ingress pref 1 proto all matchall action drop
>> >
>> > This can be fully implemented inside the relevant device driver, no
>> > changes needed in the bridge driver.
>> 
>> Interesting. Are you saying that a switchdev driver can offload rules
>> for a netdev that it does not directly control (e.g. bridge that it is
>> connected to)? It thought that you had to bind the relevant ports to the
>> same block to approximate that. If so, are any drivers implementing
>> this? I did a quick scan of mlxsw, but could not find anything obvious.
>
> Yes, currently mlxsw only supports filters configured on physical
> netdevs, but the HW can support more advanced configurations such as
> filters on a bridge device (or a VLAN upper). These would be translated
> to ACLs configured on the ingress/egress router interface (RIF) backing
> the netdev. NIC drivers support much more advanced tc offloads due to
> the prevalent usage of OVS in this space, so might be better to check
> them instead.
>
> TBH, I never looked too deeply into this, but assumed it's supported via
> the indirect tc block infra. See commit 7f76fa36754b ("net: sched:
> register callbacks for indirect tc block binds") for more details.

Thanks for this pointer! That does indeed seem possible.

For others who may be interested, the API seems to have moved a bit, but
there are a few implementations. The current entry point seems to be:

flow_indr_dev_register

> Even if I got it wrong, it would be beneficial to extend the tc offload
> infra rather than patching the bridge driver to achieve a functionality
> that is already supported in the SW data path via tc.

Agreed! I just had no idea that it was already possible.
diff mbox series

Patch

diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h
index 3aae023a9353..fa8e000a6fb9 100644
--- a/include/linux/if_bridge.h
+++ b/include/linux/if_bridge.h
@@ -157,6 +157,7 @@  static inline int br_vlan_get_info_rcu(const struct net_device *dev, u16 vid,
 struct net_device *br_fdb_find_port(const struct net_device *br_dev,
 				    const unsigned char *addr,
 				    __u16 vid);
+bool br_flood_enabled(const struct net_device *dev);
 void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
 bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
 u8 br_port_get_stp_state(const struct net_device *dev);
@@ -170,6 +171,11 @@  br_fdb_find_port(const struct net_device *br_dev,
 	return NULL;
 }
 
+static inline bool br_flood_enabled(const struct net_device *dev)
+{
+	return true;
+}
+
 static inline void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
 {
 }
diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h
index 2711c3522010..765ed70c9b28 100644
--- a/include/uapi/linux/if_bridge.h
+++ b/include/uapi/linux/if_bridge.h
@@ -72,6 +72,7 @@  struct __bridge_info {
 	__u32 tcn_timer_value;
 	__u32 topology_change_timer_value;
 	__u32 gc_timer_value;
+	__u8 flood;
 };
 
 struct __port_info {
@@ -752,13 +753,19 @@  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
+ * BR_BOOLOPT_FLOOD - control bridge flood flag
+ * BR_BOOLOPT_MCAST_FLOOD - control bridge multicast flood flag
+ * BR_BOOLOPT_BCAST_FLOOD - control bridge broadcast flood flag
  *
  * IMPORTANT: if adding a new option do not forget to handle
- *            it in br_boolopt_toggle/get and bridge sysfs
+ *            it in br_boolopt_toggle/get
  */
 enum br_boolopt_id {
 	BR_BOOLOPT_NO_LL_LEARN,
 	BR_BOOLOPT_MCAST_VLAN_SNOOPING,
+	BR_BOOLOPT_FLOOD,
+	BR_BOOLOPT_MCAST_FLOOD,
+	BR_BOOLOPT_BCAST_FLOOD,
 	BR_BOOLOPT_MAX
 };
 
diff --git a/net/bridge/br.c b/net/bridge/br.c
index b1dea3febeea..63a17bed6c63 100644
--- a/net/bridge/br.c
+++ b/net/bridge/br.c
@@ -265,6 +265,11 @@  int br_boolopt_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on,
 	case BR_BOOLOPT_MCAST_VLAN_SNOOPING:
 		err = br_multicast_toggle_vlan_snooping(br, on, extack);
 		break;
+	case BR_BOOLOPT_FLOOD:
+	case BR_BOOLOPT_MCAST_FLOOD:
+	case BR_BOOLOPT_BCAST_FLOOD:
+		err = br_flood_toggle(br, opt, on);
+		break;
 	default:
 		/* shouldn't be called with unsupported options */
 		WARN_ON(1);
@@ -281,6 +286,12 @@  int br_boolopt_get(const struct net_bridge *br, enum br_boolopt_id opt)
 		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);
+	case BR_BOOLOPT_FLOOD:
+		return br_opt_get(br, BROPT_FLOOD);
+	case BR_BOOLOPT_MCAST_FLOOD:
+		return br_opt_get(br, BROPT_MCAST_FLOOD);
+	case BR_BOOLOPT_BCAST_FLOOD:
+		return br_opt_get(br, BROPT_BCAST_FLOOD);
 	default:
 		/* shouldn't be called with unsupported options */
 		WARN_ON(1);
@@ -325,6 +336,40 @@  void br_boolopt_multi_get(const struct net_bridge *br,
 	bm->optmask = GENMASK((BR_BOOLOPT_MAX - 1), 0);
 }
 
+int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt,
+		    bool on)
+{
+	struct switchdev_attr attr = {
+		.orig_dev = br->dev,
+		.id = SWITCHDEV_ATTR_ID_BRIDGE_FLOOD,
+		.flags = SWITCHDEV_F_DEFER,
+	};
+	enum net_bridge_opts bropt;
+	int ret;
+
+	switch (opt) {
+	case BR_BOOLOPT_FLOOD:
+		bropt = BROPT_FLOOD;
+		break;
+	case BR_BOOLOPT_MCAST_FLOOD:
+		bropt = BROPT_MCAST_FLOOD;
+		break;
+	case BR_BOOLOPT_BCAST_FLOOD:
+		bropt = BROPT_BCAST_FLOOD;
+		break;
+	default:
+		WARN_ON(1);
+		return -EINVAL;
+	}
+	br_opt_toggle(br, bropt, on);
+
+	attr.u.brport_flags.mask = BIT(bropt);
+	attr.u.brport_flags.val = on << bropt;
+	ret = switchdev_port_attr_set(br->dev, &attr, NULL);
+
+	return ret;
+}
+
 /* private bridge options, controlled by the kernel */
 void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on)
 {
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 8d6bab244c4a..fafaef9d4b3a 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -524,6 +524,9 @@  void br_dev_setup(struct net_device *dev)
 	br->bridge_hello_time = br->hello_time = 2 * HZ;
 	br->bridge_forward_delay = br->forward_delay = 15 * HZ;
 	br->bridge_ageing_time = br->ageing_time = BR_DEFAULT_AGEING_TIME;
+	br_opt_toggle(br, BROPT_FLOOD, true);
+	br_opt_toggle(br, BROPT_MCAST_FLOOD, true);
+	br_opt_toggle(br, BROPT_BCAST_FLOOD, true);
 	dev->max_mtu = ETH_MAX_MTU;
 
 	br_netfilter_rtable_init(br);
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c
index e0c13fcc50ed..fcb0757bfdcc 100644
--- a/net/bridge/br_input.c
+++ b/net/bridge/br_input.c
@@ -109,11 +109,12 @@  int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 		/* by definition the broadcast is also a multicast address */
 		if (is_broadcast_ether_addr(eth_hdr(skb)->h_dest)) {
 			pkt_type = BR_PKT_BROADCAST;
-			local_rcv = true;
+			local_rcv = true && br_opt_get(br, BROPT_BCAST_FLOOD);
 		} else {
 			pkt_type = BR_PKT_MULTICAST;
-			if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
-				goto drop;
+			if (br_opt_get(br, BROPT_MCAST_FLOOD))
+				if (br_multicast_rcv(&brmctx, &pmctx, vlan, skb, vid))
+					goto drop;
 		}
 	}
 
@@ -155,9 +156,13 @@  int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 			local_rcv = true;
 			br->dev->stats.multicast++;
 		}
+		if (!br_opt_get(br, BROPT_MCAST_FLOOD))
+			local_rcv = false;
 		break;
 	case BR_PKT_UNICAST:
 		dst = br_fdb_find_rcu(br, eth_hdr(skb)->h_dest, vid);
+		if (!br_opt_get(br, BROPT_FLOOD))
+			local_rcv = false;
 		break;
 	default:
 		break;
@@ -166,7 +171,7 @@  int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 	if (dst) {
 		unsigned long now = jiffies;
 
-		if (test_bit(BR_FDB_LOCAL, &dst->flags))
+		if (test_bit(BR_FDB_LOCAL, &dst->flags) && local_rcv)
 			return br_pass_frame_up(skb);
 
 		if (now != dst->used)
@@ -190,6 +195,16 @@  int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
 }
 EXPORT_SYMBOL_GPL(br_handle_frame_finish);
 
+bool br_flood_enabled(const struct net_device *dev)
+{
+	struct net_bridge *br = netdev_priv(dev);
+
+	return !!(br_opt_get(br, BROPT_FLOOD) ||
+		   br_opt_get(br, BROPT_MCAST_FLOOD) ||
+		   br_opt_get(br, BROPT_BCAST_FLOOD));
+}
+EXPORT_SYMBOL_GPL(br_flood_enabled);
+
 static void __br_handle_local_finish(struct sk_buff *skb)
 {
 	struct net_bridge_port *p = br_port_get_rcu(skb->dev);
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 48bc61ebc211..cf88dce0b92b 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -445,6 +445,9 @@  enum net_bridge_opts {
 	BROPT_NO_LL_LEARN,
 	BROPT_VLAN_BRIDGE_BINDING,
 	BROPT_MCAST_VLAN_SNOOPING_ENABLED,
+	BROPT_FLOOD,
+	BROPT_MCAST_FLOOD,
+	BROPT_BCAST_FLOOD,
 };
 
 struct net_bridge {
@@ -720,6 +723,7 @@  int br_boolopt_multi_toggle(struct net_bridge *br,
 void br_boolopt_multi_get(const struct net_bridge *br,
 			  struct br_boolopt_multi *bm);
 void br_opt_toggle(struct net_bridge *br, enum net_bridge_opts opt, bool on);
+int br_flood_toggle(struct net_bridge *br, enum br_boolopt_id opt, bool on);
 
 /* br_device.c */
 void br_dev_setup(struct net_device *dev);