Merge branch 'bridge-add-mac-authentication-bypass-mab-support'
Ido Schimmel says: ==================== bridge: Add MAC Authentication Bypass (MAB) support Patch #1 adds MAB support in the bridge driver. See the commit message for motivation, design choices and implementation details. Patch #2 adds corresponding test cases. Follow-up patchsets will add offload support in mlxsw and mv88e6xxx. ==================== Link: https://lore.kernel.org/r/20221101193922.2125323-1-idosch@nvidia.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
commit
0884aaf37a
@ -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)
|
||||
|
||||
|
@ -561,6 +561,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)
|
||||
|
@ -52,7 +52,8 @@ 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)
|
||||
|
||||
/*
|
||||
* Neighbor Cache Entry States.
|
||||
@ -86,6 +87,11 @@ enum {
|
||||
* 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
|
||||
* 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 {
|
||||
|
@ -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,16 @@ 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_LOCKED, &fdb->flags))
|
||||
ext_flags |= NTF_EXT_LOCKED;
|
||||
|
||||
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 +177,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 +886,11 @@ 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);
|
||||
/* 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)))
|
||||
@ -1082,6 +1094,9 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (test_and_clear_bit(BR_FDB_LOCKED, &fdb->flags))
|
||||
modified = true;
|
||||
|
||||
if (fdb_handle_notify(fdb, notify))
|
||||
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_vlan *v;
|
||||
struct net_bridge *br = NULL;
|
||||
u32 ext_flags = 0;
|
||||
int err = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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]) {
|
||||
attr = tb[NDA_FDB_EXT_ATTRS];
|
||||
err = nla_parse_nested(nfea_tb, NFEA_MAX, attr,
|
||||
|
@ -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 =
|
||||
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))
|
||||
if (!fdb_src) {
|
||||
/* 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;
|
||||
} 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);
|
||||
|
@ -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,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_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_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;
|
||||
|
||||
|
@ -251,7 +251,8 @@ enum {
|
||||
BR_FDB_ADDED_BY_EXT_LEARN,
|
||||
BR_FDB_OFFLOADED,
|
||||
BR_FDB_NOTIFY,
|
||||
BR_FDB_NOTIFY_INACTIVE
|
||||
BR_FDB_NOTIFY_INACTIVE,
|
||||
BR_FDB_LOCKED,
|
||||
};
|
||||
|
||||
struct net_bridge_fdb_key {
|
||||
|
@ -4051,6 +4051,11 @@ int ndo_dflt_fdb_add(struct ndmsg *ndm,
|
||||
return err;
|
||||
}
|
||||
|
||||
if (tb[NDA_FLAGS_EXT]) {
|
||||
netdev_info(dev, "invalid flags given to default FDB implementation\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
if (vid) {
|
||||
netdev_info(dev, "vlans aren't supported yet for dev_uc|mc_add()\n");
|
||||
return err;
|
||||
|
@ -1,7 +1,16 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
ALL_TESTS="locked_port_ipv4 locked_port_ipv6 locked_port_vlan"
|
||||
ALL_TESTS="
|
||||
locked_port_ipv4
|
||||
locked_port_ipv6
|
||||
locked_port_vlan
|
||||
locked_port_mab
|
||||
locked_port_mab_roam
|
||||
locked_port_mab_config
|
||||
locked_port_mab_flush
|
||||
"
|
||||
|
||||
NUM_NETIFS=4
|
||||
CHECK_TC="no"
|
||||
source lib.sh
|
||||
@ -166,6 +175,150 @@ locked_port_ipv6()
|
||||
log_test "Locked port ipv6"
|
||||
}
|
||||
|
||||
locked_port_mab()
|
||||
{
|
||||
RET=0
|
||||
check_port_mab_support || return 0
|
||||
|
||||
ping_do $h1 192.0.2.2
|
||||
check_err $? "Ping did not work before locking port"
|
||||
|
||||
bridge link set dev $swp1 learning on locked on
|
||||
|
||||
ping_do $h1 192.0.2.2
|
||||
check_fail $? "Ping worked on a locked port without an FDB entry"
|
||||
|
||||
bridge fdb get `mac_get $h1` br br0 vlan 1 &> /dev/null
|
||||
check_fail $? "FDB entry created before enabling MAB"
|
||||
|
||||
bridge link set dev $swp1 learning on locked on mab on
|
||||
|
||||
ping_do $h1 192.0.2.2
|
||||
check_fail $? "Ping worked on MAB enabled port without an FDB entry"
|
||||
|
||||
bridge fdb get `mac_get $h1` br br0 vlan 1 | grep "dev $swp1" | grep -q "locked"
|
||||
check_err $? "Locked FDB entry not created"
|
||||
|
||||
bridge fdb replace `mac_get $h1` dev $swp1 master static
|
||||
|
||||
ping_do $h1 192.0.2.2
|
||||
check_err $? "Ping did not work after replacing FDB entry"
|
||||
|
||||
bridge fdb get `mac_get $h1` br br0 vlan 1 | grep "dev $swp1" | grep -q "locked"
|
||||
check_fail $? "FDB entry marked as locked after replacement"
|
||||
|
||||
bridge fdb del `mac_get $h1` dev $swp1 master
|
||||
bridge link set dev $swp1 learning off locked off mab off
|
||||
|
||||
log_test "Locked port MAB"
|
||||
}
|
||||
|
||||
# Check that entries cannot roam to a locked port, but that entries can roam
|
||||
# to an unlocked port.
|
||||
locked_port_mab_roam()
|
||||
{
|
||||
local mac=a0:b0:c0:c0:b0:a0
|
||||
|
||||
RET=0
|
||||
check_port_mab_support || return 0
|
||||
|
||||
bridge link set dev $swp1 learning on locked on mab on
|
||||
|
||||
$MZ $h1 -q -c 5 -d 100msec -t udp -a $mac -b rand
|
||||
bridge fdb get $mac br br0 vlan 1 | grep "dev $swp1" | grep -q "locked"
|
||||
check_err $? "No locked entry on first injection"
|
||||
|
||||
$MZ $h2 -q -c 5 -d 100msec -t udp -a $mac -b rand
|
||||
bridge fdb get $mac br br0 vlan 1 | grep -q "dev $swp2"
|
||||
check_err $? "Entry did not roam to an unlocked port"
|
||||
|
||||
bridge fdb get $mac br br0 vlan 1 | grep -q "locked"
|
||||
check_fail $? "Entry roamed with locked flag on"
|
||||
|
||||
$MZ $h1 -q -c 5 -d 100msec -t udp -a $mac -b rand
|
||||
bridge fdb get $mac br br0 vlan 1 | grep -q "dev $swp1"
|
||||
check_fail $? "Entry roamed back to locked port"
|
||||
|
||||
bridge fdb del $mac vlan 1 dev $swp2 master
|
||||
bridge link set dev $swp1 learning off locked off mab off
|
||||
|
||||
log_test "Locked port MAB roam"
|
||||
}
|
||||
|
||||
# Check that MAB can only be enabled on a port that is both locked and has
|
||||
# learning enabled.
|
||||
locked_port_mab_config()
|
||||
{
|
||||
RET=0
|
||||
check_port_mab_support || return 0
|
||||
|
||||
bridge link set dev $swp1 learning on locked off mab on &> /dev/null
|
||||
check_fail $? "MAB enabled while port is unlocked"
|
||||
|
||||
bridge link set dev $swp1 learning off locked on mab on &> /dev/null
|
||||
check_fail $? "MAB enabled while port has learning disabled"
|
||||
|
||||
bridge link set dev $swp1 learning on locked on mab on
|
||||
check_err $? "Failed to enable MAB when port is locked and has learning enabled"
|
||||
|
||||
bridge link set dev $swp1 learning off locked off mab off
|
||||
|
||||
log_test "Locked port MAB configuration"
|
||||
}
|
||||
|
||||
# Check that locked FDB entries are flushed from a port when MAB is disabled.
|
||||
locked_port_mab_flush()
|
||||
{
|
||||
local locked_mac1=00:01:02:03:04:05
|
||||
local unlocked_mac1=00:01:02:03:04:06
|
||||
local locked_mac2=00:01:02:03:04:07
|
||||
local unlocked_mac2=00:01:02:03:04:08
|
||||
|
||||
RET=0
|
||||
check_port_mab_support || return 0
|
||||
|
||||
bridge link set dev $swp1 learning on locked on mab on
|
||||
bridge link set dev $swp2 learning on locked on mab on
|
||||
|
||||
# Create regular and locked FDB entries on each port.
|
||||
bridge fdb add $unlocked_mac1 dev $swp1 vlan 1 master static
|
||||
bridge fdb add $unlocked_mac2 dev $swp2 vlan 1 master static
|
||||
|
||||
$MZ $h1 -q -c 5 -d 100msec -t udp -a $locked_mac1 -b rand
|
||||
bridge fdb get $locked_mac1 br br0 vlan 1 | grep "dev $swp1" | \
|
||||
grep -q "locked"
|
||||
check_err $? "Failed to create locked FDB entry on first port"
|
||||
|
||||
$MZ $h2 -q -c 5 -d 100msec -t udp -a $locked_mac2 -b rand
|
||||
bridge fdb get $locked_mac2 br br0 vlan 1 | grep "dev $swp2" | \
|
||||
grep -q "locked"
|
||||
check_err $? "Failed to create locked FDB entry on second port"
|
||||
|
||||
# Disable MAB on the first port and check that only the first locked
|
||||
# FDB entry was flushed.
|
||||
bridge link set dev $swp1 mab off
|
||||
|
||||
bridge fdb get $unlocked_mac1 br br0 vlan 1 &> /dev/null
|
||||
check_err $? "Regular FDB entry on first port was flushed after disabling MAB"
|
||||
|
||||
bridge fdb get $unlocked_mac2 br br0 vlan 1 &> /dev/null
|
||||
check_err $? "Regular FDB entry on second port was flushed after disabling MAB"
|
||||
|
||||
bridge fdb get $locked_mac1 br br0 vlan 1 &> /dev/null
|
||||
check_fail $? "Locked FDB entry on first port was not flushed after disabling MAB"
|
||||
|
||||
bridge fdb get $locked_mac2 br br0 vlan 1 &> /dev/null
|
||||
check_err $? "Locked FDB entry on second port was flushed after disabling MAB"
|
||||
|
||||
bridge fdb del $unlocked_mac2 dev $swp2 vlan 1 master static
|
||||
bridge fdb del $unlocked_mac1 dev $swp1 vlan 1 master static
|
||||
|
||||
bridge link set dev $swp2 learning on locked off mab off
|
||||
bridge link set dev $swp1 learning off locked off mab off
|
||||
|
||||
log_test "Locked port MAB FDB flush"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
setup_prepare
|
||||
|
@ -137,6 +137,14 @@ check_locked_port_support()
|
||||
fi
|
||||
}
|
||||
|
||||
check_port_mab_support()
|
||||
{
|
||||
if ! bridge -d link show | grep -q "mab"; then
|
||||
echo "SKIP: iproute2 too old; MacAuth feature not supported."
|
||||
return $ksft_skip
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo "SKIP: need root privileges"
|
||||
exit $ksft_skip
|
||||
|
Loading…
x
Reference in New Issue
Block a user