On 26/08/2022 14:45, Hans Schultz wrote: > Add an intermediate state for clients behind a locked port to allow for > possible opening of the port for said clients. The clients mac address > will be added with the locked flag set, denying access through the port > for the mac address, but also creating a new FDB add event giving > userspace daemons the ability to unlock the mac address. This feature > corresponds to the Mac-Auth and MAC Authentication Bypass (MAB) named > features. The latter defined by Cisco. > > As locked FDB entries are a security measure to deny access for > unauthorized hosts on specific ports, they will deny forwarding from > any port to the (MAC, vlan) pair involved and locked entries will not > be able by learning or otherwise to change the associated port. > > Only the kernel can set this FDB entry flag, while userspace can read > the flag and remove it by replacing or deleting the FDB entry. > > Locked entries will age out with the set bridge ageing time. > > Also add a 'blackhole' fdb flag, ensuring that no forwarding from any > port to a destination MAC that has a FDB entry with this flag on will > occur. The packets will thus be dropped. > > Signed-off-by: Hans Schultz <netdev@xxxxxxxxxxxxxxxxxxxx> > --- > include/linux/if_bridge.h | 1 + > include/uapi/linux/if_link.h | 1 + > include/uapi/linux/neighbour.h | 4 +++- > net/bridge/br_fdb.c | 29 +++++++++++++++++++++++++++++ > net/bridge/br_input.c | 16 +++++++++++++++- > net/bridge/br_netlink.c | 9 ++++++++- > net/bridge/br_private.h | 4 +++- > 7 files changed, 60 insertions(+), 4 deletions(-) > Hi, Please add the blackhole flag in a separate patch. A few more comments and questions below.. > diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h > index d62ef428e3aa..1668ac4d7adc 100644 > --- a/include/linux/if_bridge.h > +++ b/include/linux/if_bridge.h > @@ -59,6 +59,7 @@ struct br_ip_list { > #define BR_MRP_LOST_IN_CONT BIT(19) > #define BR_TX_FWD_OFFLOAD BIT(20) > #define BR_PORT_LOCKED BIT(21) > +#define BR_PORT_MAB BIT(22) > > #define BR_DEFAULT_AGEING_TIME (300 * HZ) > > diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h > index e36d9d2c65a7..fcbd0b85ad53 100644 > --- a/include/uapi/linux/if_link.h > +++ b/include/uapi/linux/if_link.h > @@ -560,6 +560,7 @@ enum { > IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT, > IFLA_BRPORT_MCAST_EHT_HOSTS_CNT, > IFLA_BRPORT_LOCKED, > + IFLA_BRPORT_MAB, > __IFLA_BRPORT_MAX > }; > #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) > diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h > index a998bf761635..bc1440a56b70 100644 > --- a/include/uapi/linux/neighbour.h > +++ b/include/uapi/linux/neighbour.h > @@ -52,7 +52,9 @@ enum { > #define NTF_STICKY (1 << 6) > #define NTF_ROUTER (1 << 7) > /* Extended flags under NDA_FLAGS_EXT: */ > -#define NTF_EXT_MANAGED (1 << 0) > +#define NTF_EXT_MANAGED (1 << 0) > +#define NTF_EXT_LOCKED (1 << 1) > +#define NTF_EXT_BLACKHOLE (1 << 2) > > /* > * Neighbor Cache Entry States. > diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c > index e7f4fccb6adb..1962d9594a48 100644 > --- a/net/bridge/br_fdb.c > +++ b/net/bridge/br_fdb.c > @@ -105,6 +105,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, > struct nda_cacheinfo ci; > struct nlmsghdr *nlh; > struct ndmsg *ndm; > + u32 ext_flags = 0; > > nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); > if (nlh == NULL) > @@ -125,11 +126,18 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, > ndm->ndm_flags |= NTF_EXT_LEARNED; > if (test_bit(BR_FDB_STICKY, &fdb->flags)) > ndm->ndm_flags |= NTF_STICKY; > + if (test_bit(BR_FDB_ENTRY_LOCKED, &fdb->flags)) > + ext_flags |= NTF_EXT_LOCKED; > + if (test_bit(BR_FDB_BLACKHOLE, &fdb->flags)) > + ext_flags |= NTF_EXT_BLACKHOLE; > > if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) > goto nla_put_failure; > if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex)) > goto nla_put_failure; > + if (nla_put_u32(skb, NDA_FLAGS_EXT, ext_flags)) > + goto nla_put_failure; > + > ci.ndm_used = jiffies_to_clock_t(now - fdb->used); > ci.ndm_confirmed = 0; > ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); > @@ -171,6 +179,7 @@ static inline size_t fdb_nlmsg_size(void) > return NLMSG_ALIGN(sizeof(struct ndmsg)) > + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ > + nla_total_size(sizeof(u32)) /* NDA_MASTER */ > + + nla_total_size(sizeof(u32)) /* NDA_FLAGS_EXT */ > + nla_total_size(sizeof(u16)) /* NDA_VLAN */ > + nla_total_size(sizeof(struct nda_cacheinfo)) > + nla_total_size(0) /* NDA_FDB_EXT_ATTRS */ > @@ -879,6 +888,10 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, > &fdb->flags))) > clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, > &fdb->flags); > + if (source->flags & BR_PORT_MAB) > + set_bit(BR_FDB_ENTRY_LOCKED, &fdb->flags); > + else > + clear_bit(BR_FDB_ENTRY_LOCKED, &fdb->flags); Please add a test for that bit and only then change it. > } > > if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) > @@ -1082,6 +1095,16 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, > modified = true; > } > > + if (test_bit(BR_FDB_ENTRY_LOCKED, &fdb->flags)) { > + clear_bit(BR_FDB_ENTRY_LOCKED, &fdb->flags); > + modified = true; > + } > + > + if (test_bit(BR_FDB_BLACKHOLE, &fdb->flags)) { > + clear_bit(BR_FDB_BLACKHOLE, &fdb->flags); > + modified = true; > + } > + > if (fdb_handle_notify(fdb, notify)) > modified = true; > > @@ -1178,6 +1201,12 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], > vg = nbp_vlan_group(p); > } > > + if (tb[NDA_FLAGS_EXT] && > + (nla_get_u32(tb[NDA_FLAGS_EXT]) & (NTF_EXT_LOCKED | NTF_EXT_BLACKHOLE))) { > + pr_info("bridge: RTM_NEWNEIGH has invalid extended flags\n"); > + return -EINVAL; > + } > + > if (tb[NDA_FDB_EXT_ATTRS]) { > attr = tb[NDA_FDB_EXT_ATTRS]; > err = nla_parse_nested(nfea_tb, NFEA_MAX, attr, > diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c > index 68b3e850bcb9..3d48aa7fa778 100644 > --- a/net/bridge/br_input.c > +++ b/net/bridge/br_input.c > @@ -110,8 +110,19 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb > br_fdb_find_rcu(br, eth_hdr(skb)->h_source, vid); > > if (!fdb_src || READ_ONCE(fdb_src->dst) != p || > - test_bit(BR_FDB_LOCAL, &fdb_src->flags)) > + test_bit(BR_FDB_LOCAL, &fdb_src->flags) || > + test_bit(BR_FDB_ENTRY_LOCKED, &fdb_src->flags)) { > + if (!fdb_src || (READ_ONCE(fdb_src->dst) != p && > + (p->flags & BR_LEARNING))) { please break the second part of the check on a new line instead > + unsigned long flags = 0; > + > + if (p->flags & BR_PORT_MAB) { combine this and the above as one "if" block, no need to have a new one here which preferrably starts with the MAB check > + __set_bit(BR_FDB_ENTRY_LOCKED, &flags); > + br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, flags);> + } > + } > goto drop; > + } > } > > nbp_switchdev_frame_mark(p, skb); > @@ -185,6 +196,9 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb > if (test_bit(BR_FDB_LOCAL, &dst->flags)) > return br_pass_frame_up(skb); > > + if (test_bit(BR_FDB_BLACKHOLE, &dst->flags)) > + goto drop; > + Not happy about adding a new test in arguably the most used fast-path, but I don't see a better way to do blackhole right now. Could you please make it an unlikely() ? I guess the blackhole flag will be allowed for user-space to set at some point, why not do it from the start? Actually adding a BR_FDB_LOCAL and BR_FDB_BLACKHOLE would be a conflict above - the packet will be received. So you should move the blackhole check above the BR_FDB_LOCAL one if user-space is allowed to set it to any entry. > if (now != dst->used) > dst->used = now; > br_forward(dst->dst, skb, local_rcv, false); > diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c > index 5aeb3646e74c..34483420c9e4 100644 > --- a/net/bridge/br_netlink.c > +++ b/net/bridge/br_netlink.c > @@ -188,6 +188,7 @@ static inline size_t br_port_info_size(void) > + nla_total_size(1) /* IFLA_BRPORT_NEIGH_SUPPRESS */ > + nla_total_size(1) /* IFLA_BRPORT_ISOLATED */ > + nla_total_size(1) /* IFLA_BRPORT_LOCKED */ > + + nla_total_size(1) /* IFLA_BRPORT_MAB */ > + 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 */ > @@ -274,7 +275,8 @@ static int br_port_fill_attrs(struct sk_buff *skb, > 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_LOCKED, !!(p->flags & BR_PORT_LOCKED))) > + nla_put_u8(skb, IFLA_BRPORT_LOCKED, !!(p->flags & BR_PORT_LOCKED)) || > + nla_put_u8(skb, IFLA_BRPORT_MAB, !!(p->flags & BR_PORT_MAB))) > return -EMSGSIZE; > > timerval = br_timer_value(&p->message_age_timer); > @@ -876,6 +878,7 @@ static const struct nla_policy br_port_policy[IFLA_BRPORT_MAX + 1] = { > [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NLA_U8 }, > [IFLA_BRPORT_ISOLATED] = { .type = NLA_U8 }, > [IFLA_BRPORT_LOCKED] = { .type = NLA_U8 }, > + [IFLA_BRPORT_MAB] = { .type = NLA_U8 }, > [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 }, > [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = { .type = NLA_U32 }, > }; > @@ -943,6 +946,10 @@ static int br_setport(struct net_bridge_port *p, struct nlattr *tb[], > br_set_port_flag(p, tb, IFLA_BRPORT_NEIGH_SUPPRESS, BR_NEIGH_SUPPRESS); > br_set_port_flag(p, tb, IFLA_BRPORT_ISOLATED, BR_ISOLATED); > br_set_port_flag(p, tb, IFLA_BRPORT_LOCKED, BR_PORT_LOCKED); > + br_set_port_flag(p, tb, IFLA_BRPORT_MAB, BR_PORT_MAB); > + > + if (!(p->flags & BR_PORT_LOCKED)) > + p->flags &= ~BR_PORT_MAB; > > changed_mask = old_flags ^ p->flags; > > diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h > index 06e5f6faa431..048e4afbc5a0 100644 > --- a/net/bridge/br_private.h > +++ b/net/bridge/br_private.h > @@ -251,7 +251,9 @@ enum { > BR_FDB_ADDED_BY_EXT_LEARN, > BR_FDB_OFFLOADED, > BR_FDB_NOTIFY, > - BR_FDB_NOTIFY_INACTIVE > + BR_FDB_NOTIFY_INACTIVE, > + BR_FDB_ENTRY_LOCKED, > + BR_FDB_BLACKHOLE, > }; > > struct net_bridge_fdb_key { Cheers, Nik