kernel: add a bridge feature for filtering BPDU packets on ports
authorFelix Fietkau <nbd@nbd.name>
Fri, 27 Aug 2021 10:32:00 +0000 (12:32 +0200)
committerFelix Fietkau <nbd@nbd.name>
Tue, 21 Sep 2021 17:43:20 +0000 (19:43 +0200)
This will be used to ensure that APs don't transmit unnecessary STP packets

Signed-off-by: Felix Fietkau <nbd@nbd.name>
target/linux/generic/pending-5.10/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch [new file with mode: 0644]
target/linux/generic/pending-5.4/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch [new file with mode: 0644]

diff --git a/target/linux/generic/pending-5.10/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch b/target/linux/generic/pending-5.10/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch
new file mode 100644 (file)
index 0000000..f05ff3b
--- /dev/null
@@ -0,0 +1,177 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 27 Aug 2021 12:22:32 +0200
+Subject: [PATCH] bridge: add knob for filtering rx/tx BPDU packets on a port
+
+Some devices (e.g. wireless APs) can't have devices behind them be part of
+a bridge topology with redundant links, due to address limitations.
+Additionally, broadcast traffic on these devices is somewhat expensive, due to
+the low data rate and wakeups of clients in powersave mode.
+This knob can be used to ensure that BPDU packets are never sent or forwarded
+to/from these devices
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/if_bridge.h
++++ b/include/linux/if_bridge.h
+@@ -56,6 +56,7 @@ struct br_ip_list {
+ #define BR_MRP_AWARE          BIT(17)
+ #define BR_MRP_LOST_CONT      BIT(18)
+ #define BR_MRP_LOST_IN_CONT   BIT(19)
++#define BR_BPDU_FILTER                BIT(20)
+ #define BR_DEFAULT_AGEING_TIME        (300 * HZ)
+--- a/net/bridge/br_forward.c
++++ b/net/bridge/br_forward.c
+@@ -191,6 +191,7 @@ out:
+ void br_flood(struct net_bridge *br, struct sk_buff *skb,
+             enum br_pkt_type pkt_type, bool local_rcv, bool local_orig)
+ {
++      const unsigned char *dest = eth_hdr(skb)->h_dest;
+       struct net_bridge_port *prev = NULL;
+       struct net_bridge_port *p;
+@@ -206,6 +207,10 @@ void br_flood(struct net_bridge *br, str
+               case BR_PKT_MULTICAST:
+                       if (!(p->flags & BR_MCAST_FLOOD) && skb->dev != br->dev)
+                               continue;
++                      if ((p->flags & BR_BPDU_FILTER) &&
++                          unlikely(is_link_local_ether_addr(dest) &&
++                                   dest[5] == 0))
++                              continue;
+                       break;
+               case BR_PKT_BROADCAST:
+                       if (!(p->flags & BR_BCAST_FLOOD) && skb->dev != br->dev)
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -305,6 +305,8 @@ static rx_handler_result_t br_handle_fra
+               fwd_mask |= p->group_fwd_mask;
+               switch (dest[5]) {
+               case 0x00:      /* Bridge Group Address */
++                      if (p->flags & BR_BPDU_FILTER)
++                              goto drop;
+                       /* If STP is turned off,
+                          then must forward to keep loop detection */
+                       if (p->br->stp_enabled == BR_NO_STP ||
+--- a/net/bridge/br_sysfs_if.c
++++ b/net/bridge/br_sysfs_if.c
+@@ -233,6 +233,7 @@ BRPORT_ATTR_FLAG(multicast_flood, BR_MCA
+ BRPORT_ATTR_FLAG(broadcast_flood, BR_BCAST_FLOOD);
+ BRPORT_ATTR_FLAG(neigh_suppress, BR_NEIGH_SUPPRESS);
+ BRPORT_ATTR_FLAG(isolated, BR_ISOLATED);
++BRPORT_ATTR_FLAG(bpdu_filter, BR_BPDU_FILTER);
+ #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+ static ssize_t show_multicast_router(struct net_bridge_port *p, char *buf)
+@@ -285,6 +286,7 @@ static const struct brport_attribute *br
+       &brport_attr_group_fwd_mask,
+       &brport_attr_neigh_suppress,
+       &brport_attr_isolated,
++      &brport_attr_bpdu_filter,
+       &brport_attr_backup_port,
+       NULL
+ };
+--- a/net/bridge/br_stp_bpdu.c
++++ b/net/bridge/br_stp_bpdu.c
+@@ -80,7 +80,8 @@ void br_send_config_bpdu(struct net_brid
+ {
+       unsigned char buf[35];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -127,7 +128,8 @@ void br_send_tcn_bpdu(struct net_bridge_
+ {
+       unsigned char buf[4];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -172,6 +174,9 @@ void br_stp_rcv(const struct stp_proto *
+       if (!(br->dev->flags & IFF_UP))
+               goto out;
++      if (p->flags & BR_BPDU_FILTER)
++              goto out;
++
+       if (p->state == BR_STATE_DISABLED)
+               goto out;
+--- a/include/uapi/linux/if_link.h
++++ b/include/uapi/linux/if_link.h
+@@ -524,6 +524,7 @@ enum {
+       IFLA_BRPORT_BACKUP_PORT,
+       IFLA_BRPORT_MRP_RING_OPEN,
+       IFLA_BRPORT_MRP_IN_OPEN,
++      IFLA_BRPORT_BPDU_FILTER,
+       __IFLA_BRPORT_MAX
+ };
+ #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -137,6 +137,7 @@ static inline size_t br_port_info_size(v
+               + nla_total_size(1)     /* IFLA_BRPORT_VLAN_TUNNEL */
+               + nla_total_size(1)     /* IFLA_BRPORT_NEIGH_SUPPRESS */
+               + nla_total_size(1)     /* IFLA_BRPORT_ISOLATED */
++              + nla_total_size(1)     /* IFLA_BRPORT_BPDU_FILTER */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_ROOT_ID */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */
+               + nla_total_size(sizeof(u16))   /* IFLA_BRPORT_DESIGNATED_PORT */
+@@ -220,7 +221,8 @@ static int br_port_fill_attrs(struct sk_
+                                                         BR_MRP_LOST_CONT)) ||
+           nla_put_u8(skb, IFLA_BRPORT_MRP_IN_OPEN,
+                      !!(p->flags & BR_MRP_LOST_IN_CONT)) ||
+-          nla_put_u8(skb, IFLA_BRPORT_ISOLATED, !!(p->flags & BR_ISOLATED)))
++          nla_put_u8(skb, IFLA_BRPORT_ISOLATED, !!(p->flags & BR_ISOLATED)) ||
++          nla_put_u8(skb, IFLA_BRPORT_BPDU_FILTER, !!(p->flags & BR_BPDU_FILTER)))
+               return -EMSGSIZE;
+       timerval = br_timer_value(&p->message_age_timer);
+@@ -728,6 +730,7 @@ static const struct nla_policy br_port_p
+       [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NLA_U8 },
+       [IFLA_BRPORT_ISOLATED]  = { .type = NLA_U8 },
+       [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 },
++      [IFLA_BRPORT_BPDU_FILTER] = { .type = NLA_U8 },
+ };
+ /* Change the state of the port and notify spanning tree */
+@@ -826,6 +829,10 @@ static int br_setport(struct net_bridge_
+       if (err)
+               return err;
++      err = br_set_port_flag(p, tb, IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER);
++      if (err)
++              return err;
++
+       br_vlan_tunnel_old = (p->flags & BR_VLAN_TUNNEL) ? true : false;
+       err = br_set_port_flag(p, tb, IFLA_BRPORT_VLAN_TUNNEL, BR_VLAN_TUNNEL);
+       if (err)
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -55,7 +55,7 @@
+ #include <net/net_namespace.h>
+ #define RTNL_MAX_TYPE         50
+-#define RTNL_SLAVE_MAX_TYPE   36
++#define RTNL_SLAVE_MAX_TYPE   37
+ struct rtnl_link {
+       rtnl_doit_func          doit;
+@@ -4680,7 +4680,9 @@ int ndo_dflt_bridge_getlink(struct sk_bu
+           brport_nla_put_flag(skb, flags, mask,
+                               IFLA_BRPORT_MCAST_FLOOD, BR_MCAST_FLOOD) ||
+           brport_nla_put_flag(skb, flags, mask,
+-                              IFLA_BRPORT_BCAST_FLOOD, BR_BCAST_FLOOD)) {
++                              IFLA_BRPORT_BCAST_FLOOD, BR_BCAST_FLOOD) ||
++          brport_nla_put_flag(skb, flags, mask,
++                              IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER)) {
+               nla_nest_cancel(skb, protinfo);
+               goto nla_put_failure;
+       }
diff --git a/target/linux/generic/pending-5.4/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch b/target/linux/generic/pending-5.4/710-bridge-add-knob-for-filtering-rx-tx-BPDU-pack.patch
new file mode 100644 (file)
index 0000000..90e0d0d
--- /dev/null
@@ -0,0 +1,177 @@
+From: Felix Fietkau <nbd@nbd.name>
+Date: Fri, 27 Aug 2021 12:22:32 +0200
+Subject: [PATCH] bridge: add knob for filtering rx/tx BPDU packets on a port
+
+Some devices (e.g. wireless APs) can't have devices behind them be part of
+a bridge topology with redundant links, due to address limitations.
+Additionally, broadcast traffic on these devices is somewhat expensive, due to
+the low data rate and wakeups of clients in powersave mode.
+This knob can be used to ensure that BPDU packets are never sentor forwarded
+to/from these devices
+
+Signed-off-by: Felix Fietkau <nbd@nbd.name>
+---
+
+--- a/include/linux/if_bridge.h
++++ b/include/linux/if_bridge.h
+@@ -47,6 +47,7 @@ struct br_ip_list {
+ #define BR_BCAST_FLOOD                BIT(14)
+ #define BR_NEIGH_SUPPRESS     BIT(15)
+ #define BR_ISOLATED           BIT(16)
++#define BR_BPDU_FILTER                BIT(17)
+ #define BR_DEFAULT_AGEING_TIME        (300 * HZ)
+--- a/net/bridge/br_forward.c
++++ b/net/bridge/br_forward.c
+@@ -191,6 +191,7 @@ out:
+ void br_flood(struct net_bridge *br, struct sk_buff *skb,
+             enum br_pkt_type pkt_type, bool local_rcv, bool local_orig)
+ {
++      const unsigned char *dest = eth_hdr(skb)->h_dest;
+       struct net_bridge_port *prev = NULL;
+       struct net_bridge_port *p;
+@@ -206,6 +207,10 @@ void br_flood(struct net_bridge *br, str
+               case BR_PKT_MULTICAST:
+                       if (!(p->flags & BR_MCAST_FLOOD) && skb->dev != br->dev)
+                               continue;
++                      if ((p->flags & BR_BPDU_FILTER) &&
++                          unlikely(is_link_local_ether_addr(dest) &&
++                                   dest[5] == 0))
++                              continue;
+                       break;
+               case BR_PKT_BROADCAST:
+                       if (!(p->flags & BR_BCAST_FLOOD) && skb->dev != br->dev)
+--- a/net/bridge/br_input.c
++++ b/net/bridge/br_input.c
+@@ -300,6 +300,8 @@ rx_handler_result_t br_handle_frame(stru
+               fwd_mask |= p->group_fwd_mask;
+               switch (dest[5]) {
+               case 0x00:      /* Bridge Group Address */
++                      if (p->flags & BR_BPDU_FILTER)
++                              goto drop;
+                       /* If STP is turned off,
+                          then must forward to keep loop detection */
+                       if (p->br->stp_enabled == BR_NO_STP ||
+--- a/net/bridge/br_sysfs_if.c
++++ b/net/bridge/br_sysfs_if.c
+@@ -233,6 +233,7 @@ BRPORT_ATTR_FLAG(multicast_flood, BR_MCA
+ BRPORT_ATTR_FLAG(broadcast_flood, BR_BCAST_FLOOD);
+ BRPORT_ATTR_FLAG(neigh_suppress, BR_NEIGH_SUPPRESS);
+ BRPORT_ATTR_FLAG(isolated, BR_ISOLATED);
++BRPORT_ATTR_FLAG(bpdu_filter, BR_BPDU_FILTER);
+ #ifdef CONFIG_BRIDGE_IGMP_SNOOPING
+ static ssize_t show_multicast_router(struct net_bridge_port *p, char *buf)
+@@ -285,6 +286,7 @@ static const struct brport_attribute *br
+       &brport_attr_group_fwd_mask,
+       &brport_attr_neigh_suppress,
+       &brport_attr_isolated,
++      &brport_attr_bpdu_filter,
+       &brport_attr_backup_port,
+       NULL
+ };
+--- a/net/bridge/br_stp_bpdu.c
++++ b/net/bridge/br_stp_bpdu.c
+@@ -80,7 +80,8 @@ void br_send_config_bpdu(struct net_brid
+ {
+       unsigned char buf[35];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -125,7 +126,8 @@ void br_send_tcn_bpdu(struct net_bridge_
+ {
+       unsigned char buf[4];
+-      if (p->br->stp_enabled != BR_KERNEL_STP)
++      if (p->br->stp_enabled != BR_KERNEL_STP ||
++          (p->flags & BR_BPDU_FILTER))
+               return;
+       buf[0] = 0;
+@@ -168,6 +170,9 @@ void br_stp_rcv(const struct stp_proto *
+       if (!(br->dev->flags & IFF_UP))
+               goto out;
++      if (p->flags & BR_BPDU_FILTER)
++              goto out;
++
+       if (p->state == BR_STATE_DISABLED)
+               goto out;
+--- a/include/uapi/linux/if_link.h
++++ b/include/uapi/linux/if_link.h
+@@ -340,6 +340,7 @@ enum {
+       IFLA_BRPORT_NEIGH_SUPPRESS,
+       IFLA_BRPORT_ISOLATED,
+       IFLA_BRPORT_BACKUP_PORT,
++      IFLA_BRPORT_BPDU_FILTER,
+       __IFLA_BRPORT_MAX
+ };
+ #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -137,6 +137,7 @@ static inline size_t br_port_info_size(v
+               + nla_total_size(1)     /* IFLA_BRPORT_VLAN_TUNNEL */
+               + nla_total_size(1)     /* IFLA_BRPORT_NEIGH_SUPPRESS */
+               + nla_total_size(1)     /* IFLA_BRPORT_ISOLATED */
++              + nla_total_size(1)     /* IFLA_BRPORT_BPDU_FILTER */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_ROOT_ID */
+               + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */
+               + nla_total_size(sizeof(u16))   /* IFLA_BRPORT_DESIGNATED_PORT */
+@@ -214,7 +215,8 @@ static int br_port_fill_attrs(struct sk_
+           nla_put_u16(skb, IFLA_BRPORT_GROUP_FWD_MASK, p->group_fwd_mask) ||
+           nla_put_u8(skb, IFLA_BRPORT_NEIGH_SUPPRESS,
+                      !!(p->flags & BR_NEIGH_SUPPRESS)) ||
+-          nla_put_u8(skb, IFLA_BRPORT_ISOLATED, !!(p->flags & BR_ISOLATED)))
++          nla_put_u8(skb, IFLA_BRPORT_ISOLATED, !!(p->flags & BR_ISOLATED)) ||
++          nla_put_u8(skb, IFLA_BRPORT_BPDU_FILTER, !!(p->flags & BR_BPDU_FILTER)))
+               return -EMSGSIZE;
+       timerval = br_timer_value(&p->message_age_timer);
+@@ -676,6 +678,7 @@ static const struct nla_policy br_port_p
+       [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NLA_U8 },
+       [IFLA_BRPORT_ISOLATED]  = { .type = NLA_U8 },
+       [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 },
++      [IFLA_BRPORT_BPDU_FILTER] = { .type = NLA_U8 },
+ };
+ /* Change the state of the port and notify spanning tree */
+@@ -774,6 +777,10 @@ static int br_setport(struct net_bridge_
+       if (err)
+               return err;
++      err = br_set_port_flag(p, tb, IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER);
++      if (err)
++              return err;
++
+       br_vlan_tunnel_old = (p->flags & BR_VLAN_TUNNEL) ? true : false;
+       err = br_set_port_flag(p, tb, IFLA_BRPORT_VLAN_TUNNEL, BR_VLAN_TUNNEL);
+       if (err)
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -55,7 +55,7 @@
+ #include <net/net_namespace.h>
+ #define RTNL_MAX_TYPE         50
+-#define RTNL_SLAVE_MAX_TYPE   36
++#define RTNL_SLAVE_MAX_TYPE   37
+ struct rtnl_link {
+       rtnl_doit_func          doit;
+@@ -4373,7 +4373,9 @@ int ndo_dflt_bridge_getlink(struct sk_bu
+           brport_nla_put_flag(skb, flags, mask,
+                               IFLA_BRPORT_UNICAST_FLOOD, BR_FLOOD) ||
+           brport_nla_put_flag(skb, flags, mask,
+-                              IFLA_BRPORT_PROXYARP, BR_PROXYARP)) {
++                              IFLA_BRPORT_PROXYARP, BR_PROXYARP) ||
++          brport_nla_put_flag(skb, flags, mask,
++                              IFLA_BRPORT_BPDU_FILTER, BR_BPDU_FILTER)) {
+               nla_nest_cancel(skb, protinfo);
+               goto nla_put_failure;
+       }