bridge: Add MAC Authentication Bypass (MAB) support

Hosts that support 802.1X authentication are able to authenticate
themselves by exchanging EAPOL frames with an authenticator (Ethernet
bridge, in this case) and an authentication server. Access to the
network is only granted by the authenticator to successfully
authenticated hosts.

The above is implemented in the bridge using the "locked" bridge port
option. When enabled, link-local frames (e.g., EAPOL) can be locally
received by the bridge, but all other frames are dropped unless the host
is authenticated. That is, unless the user space control plane installed
an FDB entry according to which the source address of the frame is
located behind the locked ingress port. The entry can be dynamic, in
which case learning needs to be enabled so that the entry will be
refreshed by incoming traffic.

There are deployments in which not all the devices connected to the
authenticator (the bridge) support 802.1X. Such devices can include
printers and cameras. One option to support such deployments is to
unlock the bridge ports connecting these devices, but a slightly more
secure option is to use MAB. When MAB is enabled, the MAC address of the
connected device is used as the user name and password for the
authentication.

For MAB to work, the user space control plane needs to be notified about
MAC addresses that are trying to gain access so that they will be
compared against an allow list. This can be implemented via the regular
learning process with the sole difference that learned FDB entries are
installed with a new "locked" flag indicating that the entry cannot be
used to authenticate the device. The flag cannot be set by user space,
but user space can clear the flag by replacing the entry, thereby
authenticating the device.

Locked FDB entries implement the following semantics with regards to
roaming, aging and forwarding:

1. Roaming: Locked FDB entries can roam to unlocked (authorized) ports,
   in which case the "locked" flag is cleared. FDB entries cannot roam
   to locked ports regardless of MAB being enabled or not. Therefore,
   locked FDB entries are only created if an FDB entry with the given {MAC,
   VID} does not already exist. This behavior prevents unauthenticated
   devices from disrupting traffic destined to already authenticated
   devices.

2. Aging: Locked FDB entries age and refresh by incoming traffic like
   regular entries.

3. Forwarding: Locked FDB entries forward traffic like regular entries.
   If user space detects an unauthorized MAC behind a locked port and
   wishes to prevent traffic with this MAC DA from reaching the host, it
   can do so using tc or a different mechanism.

Enable the above behavior using a new bridge port option called "mab".
It can only be enabled on a bridge port that is both locked and has
learning enabled. Locked FDB entries are flushed from the port once MAB
is disabled. A new option is added because there are pure 802.1X
deployments that are not interested in notifications about locked FDB
entries.

Signed-off-by: Hans J. Schultz <netdev@kapio-technology.com>
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
Reviewed-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Hans J. Schultz 2022-11-01 21:39:21 +02:00 committed by Jakub Kicinski
parent fbeb229a66
commit a35ec8e38c
8 changed files with 79 additions and 5 deletions

View File

@ -59,6 +59,7 @@ struct br_ip_list {
#define BR_MRP_LOST_IN_CONT BIT(19) #define BR_MRP_LOST_IN_CONT BIT(19)
#define BR_TX_FWD_OFFLOAD BIT(20) #define BR_TX_FWD_OFFLOAD BIT(20)
#define BR_PORT_LOCKED BIT(21) #define BR_PORT_LOCKED BIT(21)
#define BR_PORT_MAB BIT(22)
#define BR_DEFAULT_AGEING_TIME (300 * HZ) #define BR_DEFAULT_AGEING_TIME (300 * HZ)

View File

@ -561,6 +561,7 @@ enum {
IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT, IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT,
IFLA_BRPORT_MCAST_EHT_HOSTS_CNT, IFLA_BRPORT_MCAST_EHT_HOSTS_CNT,
IFLA_BRPORT_LOCKED, IFLA_BRPORT_LOCKED,
IFLA_BRPORT_MAB,
__IFLA_BRPORT_MAX __IFLA_BRPORT_MAX
}; };
#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)

View File

@ -53,6 +53,7 @@ enum {
#define NTF_ROUTER (1 << 7) #define NTF_ROUTER (1 << 7)
/* Extended flags under NDA_FLAGS_EXT: */ /* Extended flags under NDA_FLAGS_EXT: */
#define NTF_EXT_MANAGED (1 << 0) #define NTF_EXT_MANAGED (1 << 0)
#define NTF_EXT_LOCKED (1 << 1)
/* /*
* Neighbor Cache Entry States. * Neighbor Cache Entry States.
@ -86,6 +87,11 @@ enum {
* NTF_EXT_MANAGED flagged neigbor entries are managed by the kernel on behalf * NTF_EXT_MANAGED flagged neigbor entries are managed by the kernel on behalf
* of a user space control plane, and automatically refreshed so that (if * of a user space control plane, and automatically refreshed so that (if
* possible) they remain in NUD_REACHABLE state. * possible) they remain in NUD_REACHABLE state.
*
* NTF_EXT_LOCKED flagged bridge FDB entries are entries generated by the
* bridge in response to a host trying to communicate via a locked bridge port
* with MAB enabled. Their purpose is to notify user space that a host requires
* authentication.
*/ */
struct nda_cacheinfo { struct nda_cacheinfo {

View File

@ -105,6 +105,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
struct nda_cacheinfo ci; struct nda_cacheinfo ci;
struct nlmsghdr *nlh; struct nlmsghdr *nlh;
struct ndmsg *ndm; struct ndmsg *ndm;
u32 ext_flags = 0;
nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags);
if (nlh == NULL) if (nlh == NULL)
@ -125,11 +126,16 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
ndm->ndm_flags |= NTF_EXT_LEARNED; ndm->ndm_flags |= NTF_EXT_LEARNED;
if (test_bit(BR_FDB_STICKY, &fdb->flags)) if (test_bit(BR_FDB_STICKY, &fdb->flags))
ndm->ndm_flags |= NTF_STICKY; ndm->ndm_flags |= NTF_STICKY;
if (test_bit(BR_FDB_LOCKED, &fdb->flags))
ext_flags |= NTF_EXT_LOCKED;
if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr))
goto nla_put_failure; goto nla_put_failure;
if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex)) if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex))
goto nla_put_failure; 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_used = jiffies_to_clock_t(now - fdb->used);
ci.ndm_confirmed = 0; ci.ndm_confirmed = 0;
ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated);
@ -171,6 +177,7 @@ static inline size_t fdb_nlmsg_size(void)
return NLMSG_ALIGN(sizeof(struct ndmsg)) return NLMSG_ALIGN(sizeof(struct ndmsg))
+ nla_total_size(ETH_ALEN) /* NDA_LLADDR */ + nla_total_size(ETH_ALEN) /* NDA_LLADDR */
+ nla_total_size(sizeof(u32)) /* NDA_MASTER */ + 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(u16)) /* NDA_VLAN */
+ nla_total_size(sizeof(struct nda_cacheinfo)) + nla_total_size(sizeof(struct nda_cacheinfo))
+ nla_total_size(0) /* NDA_FDB_EXT_ATTRS */ + nla_total_size(0) /* NDA_FDB_EXT_ATTRS */
@ -879,6 +886,11 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
&fdb->flags))) &fdb->flags)))
clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, clear_bit(BR_FDB_ADDED_BY_EXT_LEARN,
&fdb->flags); &fdb->flags);
/* Clear locked flag when roaming to an
* unlocked port.
*/
if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags)))
clear_bit(BR_FDB_LOCKED, &fdb->flags);
} }
if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags)))
@ -1082,6 +1094,9 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
modified = true; modified = true;
} }
if (test_and_clear_bit(BR_FDB_LOCKED, &fdb->flags))
modified = true;
if (fdb_handle_notify(fdb, notify)) if (fdb_handle_notify(fdb, notify))
modified = true; modified = true;
@ -1150,6 +1165,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
struct net_bridge_port *p = NULL; struct net_bridge_port *p = NULL;
struct net_bridge_vlan *v; struct net_bridge_vlan *v;
struct net_bridge *br = NULL; struct net_bridge *br = NULL;
u32 ext_flags = 0;
int err = 0; int err = 0;
trace_br_fdb_add(ndm, dev, addr, vid, nlh_flags); trace_br_fdb_add(ndm, dev, addr, vid, nlh_flags);
@ -1178,6 +1194,14 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
vg = nbp_vlan_group(p); vg = nbp_vlan_group(p);
} }
if (tb[NDA_FLAGS_EXT])
ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]);
if (ext_flags & NTF_EXT_LOCKED) {
NL_SET_ERR_MSG_MOD(extack, "Cannot add FDB entry with \"locked\" flag set");
return -EINVAL;
}
if (tb[NDA_FDB_EXT_ATTRS]) { if (tb[NDA_FDB_EXT_ATTRS]) {
attr = tb[NDA_FDB_EXT_ATTRS]; attr = tb[NDA_FDB_EXT_ATTRS];
err = nla_parse_nested(nfea_tb, NFEA_MAX, attr, err = nla_parse_nested(nfea_tb, NFEA_MAX, attr,

View File

@ -109,9 +109,26 @@ int br_handle_frame_finish(struct net *net, struct sock *sk, struct sk_buff *skb
struct net_bridge_fdb_entry *fdb_src = struct net_bridge_fdb_entry *fdb_src =
br_fdb_find_rcu(br, eth_hdr(skb)->h_source, vid); br_fdb_find_rcu(br, eth_hdr(skb)->h_source, vid);
if (!fdb_src || READ_ONCE(fdb_src->dst) != p || if (!fdb_src) {
test_bit(BR_FDB_LOCAL, &fdb_src->flags)) /* FDB miss. Create locked FDB entry if MAB is enabled
* and drop the packet.
*/
if (p->flags & BR_PORT_MAB)
br_fdb_update(br, p, eth_hdr(skb)->h_source,
vid, BIT(BR_FDB_LOCKED));
goto drop; goto drop;
} else if (READ_ONCE(fdb_src->dst) != p ||
test_bit(BR_FDB_LOCAL, &fdb_src->flags)) {
/* FDB mismatch. Drop the packet without roaming. */
goto drop;
} else if test_bit(BR_FDB_LOCKED, &fdb_src->flags) {
/* FDB match, but entry is locked. Refresh it and drop
* the packet.
*/
br_fdb_update(br, p, eth_hdr(skb)->h_source, vid,
BIT(BR_FDB_LOCKED));
goto drop;
}
} }
nbp_switchdev_frame_mark(p, skb); nbp_switchdev_frame_mark(p, skb);

View File

@ -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_NEIGH_SUPPRESS */
+ nla_total_size(1) /* IFLA_BRPORT_ISOLATED */ + nla_total_size(1) /* IFLA_BRPORT_ISOLATED */
+ nla_total_size(1) /* IFLA_BRPORT_LOCKED */ + 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_ROOT_ID */
+ nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */ + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */
+ nla_total_size(sizeof(u16)) /* IFLA_BRPORT_DESIGNATED_PORT */ + 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, nla_put_u8(skb, IFLA_BRPORT_MRP_IN_OPEN,
!!(p->flags & BR_MRP_LOST_IN_CONT)) || !!(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_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; return -EMSGSIZE;
timerval = br_timer_value(&p->message_age_timer); 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_NEIGH_SUPPRESS] = { .type = NLA_U8 },
[IFLA_BRPORT_ISOLATED] = { .type = NLA_U8 }, [IFLA_BRPORT_ISOLATED] = { .type = NLA_U8 },
[IFLA_BRPORT_LOCKED] = { .type = NLA_U8 }, [IFLA_BRPORT_LOCKED] = { .type = NLA_U8 },
[IFLA_BRPORT_MAB] = { .type = NLA_U8 },
[IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 }, [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 },
[IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = { .type = NLA_U32 }, [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = { .type = NLA_U32 },
}; };
@ -943,6 +946,22 @@ 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_NEIGH_SUPPRESS, BR_NEIGH_SUPPRESS);
br_set_port_flag(p, tb, IFLA_BRPORT_ISOLATED, BR_ISOLATED); 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_LOCKED, BR_PORT_LOCKED);
br_set_port_flag(p, tb, IFLA_BRPORT_MAB, BR_PORT_MAB);
if ((p->flags & BR_PORT_MAB) &&
(!(p->flags & BR_PORT_LOCKED) || !(p->flags & BR_LEARNING))) {
NL_SET_ERR_MSG(extack, "Bridge port must be locked and have learning enabled when MAB is enabled");
p->flags = old_flags;
return -EINVAL;
} else if (!(p->flags & BR_PORT_MAB) && (old_flags & BR_PORT_MAB)) {
struct net_bridge_fdb_flush_desc desc = {
.flags = BIT(BR_FDB_LOCKED),
.flags_mask = BIT(BR_FDB_LOCKED),
.port_ifindex = p->dev->ifindex,
};
br_fdb_flush(p->br, &desc);
}
changed_mask = old_flags ^ p->flags; changed_mask = old_flags ^ p->flags;

View File

@ -251,7 +251,8 @@ enum {
BR_FDB_ADDED_BY_EXT_LEARN, BR_FDB_ADDED_BY_EXT_LEARN,
BR_FDB_OFFLOADED, BR_FDB_OFFLOADED,
BR_FDB_NOTIFY, BR_FDB_NOTIFY,
BR_FDB_NOTIFY_INACTIVE BR_FDB_NOTIFY_INACTIVE,
BR_FDB_LOCKED,
}; };
struct net_bridge_fdb_key { struct net_bridge_fdb_key {

View File

@ -4051,6 +4051,11 @@ int ndo_dflt_fdb_add(struct ndmsg *ndm,
return err; return err;
} }
if (tb[NDA_FLAGS_EXT]) {
netdev_info(dev, "invalid flags given to default FDB implementation\n");
return err;
}
if (vid) { if (vid) {
netdev_info(dev, "vlans aren't supported yet for dev_uc|mc_add()\n"); netdev_info(dev, "vlans aren't supported yet for dev_uc|mc_add()\n");
return err; return err;