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 <[email protected]>
> ---
>  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

Reply via email to