Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Hans J. Schultz and committed by
Jakub Kicinski
a35ec8e3 fbeb229a

+79 -5
+1
include/linux/if_bridge.h
··· 59 59 #define BR_MRP_LOST_IN_CONT BIT(19) 60 60 #define BR_TX_FWD_OFFLOAD BIT(20) 61 61 #define BR_PORT_LOCKED BIT(21) 62 + #define BR_PORT_MAB BIT(22) 62 63 63 64 #define BR_DEFAULT_AGEING_TIME (300 * HZ) 64 65
+1
include/uapi/linux/if_link.h
··· 561 561 IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT, 562 562 IFLA_BRPORT_MCAST_EHT_HOSTS_CNT, 563 563 IFLA_BRPORT_LOCKED, 564 + IFLA_BRPORT_MAB, 564 565 __IFLA_BRPORT_MAX 565 566 }; 566 567 #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1)
+7 -1
include/uapi/linux/neighbour.h
··· 52 52 #define NTF_STICKY (1 << 6) 53 53 #define NTF_ROUTER (1 << 7) 54 54 /* Extended flags under NDA_FLAGS_EXT: */ 55 - #define NTF_EXT_MANAGED (1 << 0) 55 + #define NTF_EXT_MANAGED (1 << 0) 56 + #define NTF_EXT_LOCKED (1 << 1) 56 57 57 58 /* 58 59 * Neighbor Cache Entry States. ··· 87 86 * NTF_EXT_MANAGED flagged neigbor entries are managed by the kernel on behalf 88 87 * of a user space control plane, and automatically refreshed so that (if 89 88 * possible) they remain in NUD_REACHABLE state. 89 + * 90 + * NTF_EXT_LOCKED flagged bridge FDB entries are entries generated by the 91 + * bridge in response to a host trying to communicate via a locked bridge port 92 + * with MAB enabled. Their purpose is to notify user space that a host requires 93 + * authentication. 90 94 */ 91 95 92 96 struct nda_cacheinfo {
+24
net/bridge/br_fdb.c
··· 105 105 struct nda_cacheinfo ci; 106 106 struct nlmsghdr *nlh; 107 107 struct ndmsg *ndm; 108 + u32 ext_flags = 0; 108 109 109 110 nlh = nlmsg_put(skb, portid, seq, type, sizeof(*ndm), flags); 110 111 if (nlh == NULL) ··· 126 125 ndm->ndm_flags |= NTF_EXT_LEARNED; 127 126 if (test_bit(BR_FDB_STICKY, &fdb->flags)) 128 127 ndm->ndm_flags |= NTF_STICKY; 128 + if (test_bit(BR_FDB_LOCKED, &fdb->flags)) 129 + ext_flags |= NTF_EXT_LOCKED; 129 130 130 131 if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr)) 131 132 goto nla_put_failure; 132 133 if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex)) 133 134 goto nla_put_failure; 135 + if (nla_put_u32(skb, NDA_FLAGS_EXT, ext_flags)) 136 + goto nla_put_failure; 137 + 134 138 ci.ndm_used = jiffies_to_clock_t(now - fdb->used); 135 139 ci.ndm_confirmed = 0; 136 140 ci.ndm_updated = jiffies_to_clock_t(now - fdb->updated); ··· 177 171 return NLMSG_ALIGN(sizeof(struct ndmsg)) 178 172 + nla_total_size(ETH_ALEN) /* NDA_LLADDR */ 179 173 + nla_total_size(sizeof(u32)) /* NDA_MASTER */ 174 + + nla_total_size(sizeof(u32)) /* NDA_FLAGS_EXT */ 180 175 + nla_total_size(sizeof(u16)) /* NDA_VLAN */ 181 176 + nla_total_size(sizeof(struct nda_cacheinfo)) 182 177 + nla_total_size(0) /* NDA_FDB_EXT_ATTRS */ ··· 886 879 &fdb->flags))) 887 880 clear_bit(BR_FDB_ADDED_BY_EXT_LEARN, 888 881 &fdb->flags); 882 + /* Clear locked flag when roaming to an 883 + * unlocked port. 884 + */ 885 + if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags))) 886 + clear_bit(BR_FDB_LOCKED, &fdb->flags); 889 887 } 890 888 891 889 if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) ··· 1094 1082 modified = true; 1095 1083 } 1096 1084 1085 + if (test_and_clear_bit(BR_FDB_LOCKED, &fdb->flags)) 1086 + modified = true; 1087 + 1097 1088 if (fdb_handle_notify(fdb, notify)) 1098 1089 modified = true; 1099 1090 ··· 1165 1150 struct net_bridge_port *p = NULL; 1166 1151 struct net_bridge_vlan *v; 1167 1152 struct net_bridge *br = NULL; 1153 + u32 ext_flags = 0; 1168 1154 int err = 0; 1169 1155 1170 1156 trace_br_fdb_add(ndm, dev, addr, vid, nlh_flags); ··· 1192 1176 } 1193 1177 br = p->br; 1194 1178 vg = nbp_vlan_group(p); 1179 + } 1180 + 1181 + if (tb[NDA_FLAGS_EXT]) 1182 + ext_flags = nla_get_u32(tb[NDA_FLAGS_EXT]); 1183 + 1184 + if (ext_flags & NTF_EXT_LOCKED) { 1185 + NL_SET_ERR_MSG_MOD(extack, "Cannot add FDB entry with \"locked\" flag set"); 1186 + return -EINVAL; 1195 1187 } 1196 1188 1197 1189 if (tb[NDA_FDB_EXT_ATTRS]) {
+19 -2
net/bridge/br_input.c
··· 109 109 struct net_bridge_fdb_entry *fdb_src = 110 110 br_fdb_find_rcu(br, eth_hdr(skb)->h_source, vid); 111 111 112 - if (!fdb_src || READ_ONCE(fdb_src->dst) != p || 113 - test_bit(BR_FDB_LOCAL, &fdb_src->flags)) 112 + if (!fdb_src) { 113 + /* FDB miss. Create locked FDB entry if MAB is enabled 114 + * and drop the packet. 115 + */ 116 + if (p->flags & BR_PORT_MAB) 117 + br_fdb_update(br, p, eth_hdr(skb)->h_source, 118 + vid, BIT(BR_FDB_LOCKED)); 114 119 goto drop; 120 + } else if (READ_ONCE(fdb_src->dst) != p || 121 + test_bit(BR_FDB_LOCAL, &fdb_src->flags)) { 122 + /* FDB mismatch. Drop the packet without roaming. */ 123 + goto drop; 124 + } else if test_bit(BR_FDB_LOCKED, &fdb_src->flags) { 125 + /* FDB match, but entry is locked. Refresh it and drop 126 + * the packet. 127 + */ 128 + br_fdb_update(br, p, eth_hdr(skb)->h_source, vid, 129 + BIT(BR_FDB_LOCKED)); 130 + goto drop; 131 + } 115 132 } 116 133 117 134 nbp_switchdev_frame_mark(p, skb);
+20 -1
net/bridge/br_netlink.c
··· 188 188 + nla_total_size(1) /* IFLA_BRPORT_NEIGH_SUPPRESS */ 189 189 + nla_total_size(1) /* IFLA_BRPORT_ISOLATED */ 190 190 + nla_total_size(1) /* IFLA_BRPORT_LOCKED */ 191 + + nla_total_size(1) /* IFLA_BRPORT_MAB */ 191 192 + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_ROOT_ID */ 192 193 + nla_total_size(sizeof(struct ifla_bridge_id)) /* IFLA_BRPORT_BRIDGE_ID */ 193 194 + nla_total_size(sizeof(u16)) /* IFLA_BRPORT_DESIGNATED_PORT */ ··· 275 274 nla_put_u8(skb, IFLA_BRPORT_MRP_IN_OPEN, 276 275 !!(p->flags & BR_MRP_LOST_IN_CONT)) || 277 276 nla_put_u8(skb, IFLA_BRPORT_ISOLATED, !!(p->flags & BR_ISOLATED)) || 278 - nla_put_u8(skb, IFLA_BRPORT_LOCKED, !!(p->flags & BR_PORT_LOCKED))) 277 + nla_put_u8(skb, IFLA_BRPORT_LOCKED, !!(p->flags & BR_PORT_LOCKED)) || 278 + nla_put_u8(skb, IFLA_BRPORT_MAB, !!(p->flags & BR_PORT_MAB))) 279 279 return -EMSGSIZE; 280 280 281 281 timerval = br_timer_value(&p->message_age_timer); ··· 878 876 [IFLA_BRPORT_NEIGH_SUPPRESS] = { .type = NLA_U8 }, 879 877 [IFLA_BRPORT_ISOLATED] = { .type = NLA_U8 }, 880 878 [IFLA_BRPORT_LOCKED] = { .type = NLA_U8 }, 879 + [IFLA_BRPORT_MAB] = { .type = NLA_U8 }, 881 880 [IFLA_BRPORT_BACKUP_PORT] = { .type = NLA_U32 }, 882 881 [IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT] = { .type = NLA_U32 }, 883 882 }; ··· 946 943 br_set_port_flag(p, tb, IFLA_BRPORT_NEIGH_SUPPRESS, BR_NEIGH_SUPPRESS); 947 944 br_set_port_flag(p, tb, IFLA_BRPORT_ISOLATED, BR_ISOLATED); 948 945 br_set_port_flag(p, tb, IFLA_BRPORT_LOCKED, BR_PORT_LOCKED); 946 + br_set_port_flag(p, tb, IFLA_BRPORT_MAB, BR_PORT_MAB); 947 + 948 + if ((p->flags & BR_PORT_MAB) && 949 + (!(p->flags & BR_PORT_LOCKED) || !(p->flags & BR_LEARNING))) { 950 + NL_SET_ERR_MSG(extack, "Bridge port must be locked and have learning enabled when MAB is enabled"); 951 + p->flags = old_flags; 952 + return -EINVAL; 953 + } else if (!(p->flags & BR_PORT_MAB) && (old_flags & BR_PORT_MAB)) { 954 + struct net_bridge_fdb_flush_desc desc = { 955 + .flags = BIT(BR_FDB_LOCKED), 956 + .flags_mask = BIT(BR_FDB_LOCKED), 957 + .port_ifindex = p->dev->ifindex, 958 + }; 959 + 960 + br_fdb_flush(p->br, &desc); 961 + } 949 962 950 963 changed_mask = old_flags ^ p->flags; 951 964
+2 -1
net/bridge/br_private.h
··· 251 251 BR_FDB_ADDED_BY_EXT_LEARN, 252 252 BR_FDB_OFFLOADED, 253 253 BR_FDB_NOTIFY, 254 - BR_FDB_NOTIFY_INACTIVE 254 + BR_FDB_NOTIFY_INACTIVE, 255 + BR_FDB_LOCKED, 255 256 }; 256 257 257 258 struct net_bridge_fdb_key {
+5
net/core/rtnetlink.c
··· 4051 4051 return err; 4052 4052 } 4053 4053 4054 + if (tb[NDA_FLAGS_EXT]) { 4055 + netdev_info(dev, "invalid flags given to default FDB implementation\n"); 4056 + return err; 4057 + } 4058 + 4054 4059 if (vid) { 4055 4060 netdev_info(dev, "vlans aren't supported yet for dev_uc|mc_add()\n"); 4056 4061 return err;