Skip to content

Commit

Permalink
Merge branch 'bridge-add-mac-authentication-bypass-mab-support'
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Jakub Kicinski committed Nov 4, 2022
2 parents fbeb229 + 4a331d3 commit 0884aaf
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 6 deletions.
1 change: 1 addition & 0 deletions include/linux/if_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
1 change: 1 addition & 0 deletions include/uapi/linux/if_link.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion include/uapi/linux/neighbour.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 24 additions & 0 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down
21 changes: 19 additions & 2 deletions net/bridge/br_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
21 changes: 20 additions & 1 deletion net/bridge/br_netlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 },
};
Expand Down Expand Up @@ -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;

Expand Down
3 changes: 2 additions & 1 deletion net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions net/core/rtnetlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
155 changes: 154 additions & 1 deletion tools/testing/selftests/net/forwarding/bridge_locked_port.sh
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions tools/testing/selftests/net/forwarding/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 0884aaf

Please sign in to comment.