Skip to content

Commit 478a85f

Browse files
committed
net: dsa: flush learned FDB on user port bulk delete
Implement ndo_fdb_del_bulk for DSA so that a bulk RTM_DELNEIGH with NTF_SELF can flush a switch's learned FDB. This is required by user space applications implementing MRP (802.1Q). The NTF_MASTER leg of the same request cannot serve this. Where learning is offloaded the switch learns in hardware and the entries were never in the bridge FDB, so the bridge has nothing to delete and nothing to notify back down to the driver. Fast ageing deletes the dynamically learned entries and nothing else, which bounds what the handler can accept. ndm_state and ndm_flags select entries by flag, so any bit set in them asks for entries that fast ageing cannot delete; refuse the request rather than answer it by deleting the entries the caller asked to keep. A mask is not the same thing, since with the bit clear in the request it selects the entries which do not carry that flag: a dynamically learned entry carries neither of the ndm_state flags nor NTF_USE, NTF_EXT_LEARNED or NTF_STICKY, so a mask naming those is satisfied by definition, while NTF_OFFLOADED describes every entry in a switch and a mask naming it selects nothing that can be deleted. The validation otherwise follows br_fdb_delete_bulk() and vxlan_fdb_delete_bulk(), and reports -EOPNOTSUPP rather than -EINVAL throughout: the request is well formed, it is just not one this port can serve, and the NTF_MASTER leg may already have flushed the bridge by the time it is refused. NDA_IFINDEX scopes a bridge flush to one of its ports, so here it has to name the port the request was sent to. A LAG or HSR member is refused outright, because its bridge port is the aggregate device: the flush could not be notified without also dropping the addresses its siblings learned. An unfiltered request asks for the static entries too. Deleting those behind the bridge's back would leave it believing they are still programmed, so they are left alone and only the dynamically learned entries go. Since ds->ops->port_fast_age() returns void, a whole-port flush which the driver declines or fails to carry out cannot be reported either. As on the other fast age paths, the switchdev flush notification also drops the bridge's dynamic entries for the port, keeping the software FDB in step with hardware, and the flush covers this port on this switch only: addresses that other switches in the tree learned on their DSA links are left alone. Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Luke Howard <lukeh@padl.com>
1 parent f334ef1 commit 478a85f

3 files changed

Lines changed: 107 additions & 0 deletions

File tree

net/dsa/port.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ static int dsa_port_vlan_fast_age(const struct dsa_port *dp, u16 vid)
8181
return err;
8282
}
8383

84+
/* Flush this port's dynamically learned FDB entries, scoped to a VLAN when
85+
* one is given. Widening a VLAN-scoped request to the whole port would delete
86+
* entries the caller asked to keep, so a switch without VLAN-scoped fast age
87+
* refuses it instead.
88+
*/
89+
int dsa_port_flush_dynamic_fdb(const struct dsa_port *dp, u16 vid)
90+
{
91+
struct dsa_switch *ds = dp->ds;
92+
93+
if (vid)
94+
return dsa_port_vlan_fast_age(dp, vid);
95+
96+
if (!ds->ops->port_fast_age)
97+
return -EOPNOTSUPP;
98+
99+
dsa_port_fast_age(dp);
100+
101+
return 0;
102+
}
103+
84104
static int dsa_port_msti_fast_age(const struct dsa_port *dp, u16 msti)
85105
{
86106
DECLARE_BITMAP(vids, VLAN_N_VID) = { 0 };

net/dsa/port.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ bool dsa_port_supports_hwtstamp(struct dsa_port *dp);
1919
void dsa_port_set_tag_protocol(struct dsa_port *cpu_dp,
2020
const struct dsa_device_ops *tag_ops);
2121
int dsa_port_set_state(struct dsa_port *dp, u8 state, bool do_fast_age);
22+
int dsa_port_flush_dynamic_fdb(const struct dsa_port *dp, u16 vid);
2223
int dsa_port_set_mst_state(struct dsa_port *dp,
2324
const struct switchdev_mst_state *state,
2425
struct netlink_ext_ack *extack);

net/dsa/user.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,91 @@ dsa_user_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
575575
return err;
576576
}
577577

578+
static const struct nla_policy dsa_user_fdb_del_bulk_policy[NDA_MAX + 1] = {
579+
[NDA_VLAN] = NLA_POLICY_RANGE(NLA_U16, 1, VLAN_N_VID - 2),
580+
[NDA_IFINDEX] = NLA_POLICY_MIN(NLA_S32, 1),
581+
[NDA_NDM_STATE_MASK] = { .type = NLA_U16 },
582+
[NDA_NDM_FLAGS_MASK] = { .type = NLA_U8 },
583+
};
584+
585+
#define DSA_FDB_FLUSH_IGNORED_NDM_FLAGS (NTF_MASTER | NTF_SELF)
586+
587+
/* Fast ageing deletes the dynamically learned entries and nothing else, so no
588+
* ndm_state or ndm_flags bit selects anything it can delete. A mask is not the
589+
* same thing: with the bit clear in the request it selects the entries which
590+
* do not carry that flag, and a dynamically learned entry carries none of the
591+
* ones below, so such a mask is satisfied by definition. NTF_OFFLOADED is not
592+
* among them, as every entry in a switch is offloaded, so a mask naming it
593+
* selects nothing that can be deleted.
594+
*/
595+
#define DSA_FDB_FLUSH_ALLOWED_NDM_STATES 0
596+
#define DSA_FDB_FLUSH_ALLOWED_NDM_FLAGS 0
597+
#define DSA_FDB_FLUSH_ALLOWED_NDM_FLAGS_MASK \
598+
(NTF_USE | NTF_EXT_LEARNED | NTF_STICKY)
599+
600+
static int dsa_user_fdb_del_bulk(struct nlmsghdr *nlh, struct net_device *dev,
601+
struct netlink_ext_ack *extack)
602+
{
603+
const struct dsa_port *dp = dsa_user_to_port(dev);
604+
struct ndmsg *ndm = nlmsg_data(nlh);
605+
struct nlattr *tb[NDA_MAX + 1];
606+
u8 ndm_flags;
607+
u16 vid = 0;
608+
int err;
609+
610+
ndm_flags = ndm->ndm_flags & ~DSA_FDB_FLUSH_IGNORED_NDM_FLAGS;
611+
612+
err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX,
613+
dsa_user_fdb_del_bulk_policy, extack);
614+
if (err)
615+
return err;
616+
617+
/* Refuse a request which selects entries that cannot be deleted, rather
618+
* than answer it by deleting the entries it asked to keep.
619+
*/
620+
if (ndm_flags & ~DSA_FDB_FLUSH_ALLOWED_NDM_FLAGS) {
621+
NL_SET_ERR_MSG_MOD(extack,
622+
"Unsupported fdb flush ndm flag bits set");
623+
return -EOPNOTSUPP;
624+
}
625+
if (ndm->ndm_state & ~DSA_FDB_FLUSH_ALLOWED_NDM_STATES) {
626+
NL_SET_ERR_MSG_MOD(extack,
627+
"Unsupported fdb flush ndm state bits set");
628+
return -EOPNOTSUPP;
629+
}
630+
if (tb[NDA_NDM_FLAGS_MASK] &&
631+
(nla_get_u8(tb[NDA_NDM_FLAGS_MASK]) &
632+
~DSA_FDB_FLUSH_ALLOWED_NDM_FLAGS_MASK)) {
633+
NL_SET_ERR_MSG_MOD(extack,
634+
"Unsupported fdb flush ndm flag mask bits set");
635+
return -EOPNOTSUPP;
636+
}
637+
638+
/* The bridge port of a LAG or HSR member is the aggregate device, so a
639+
* flush of one member cannot be told to the bridge without dropping the
640+
* addresses its siblings learned as well.
641+
*/
642+
if (dp->lag || dp->hsr_dev) {
643+
NL_SET_ERR_MSG_MOD(extack,
644+
"Cannot flush a LAG or HSR member on its own");
645+
return -EOPNOTSUPP;
646+
}
647+
648+
/* The bridge uses NDA_IFINDEX to scope a flush to one of its ports.
649+
* Here the port is the device the request was sent to.
650+
*/
651+
if (tb[NDA_IFINDEX] && nla_get_s32(tb[NDA_IFINDEX]) != dev->ifindex) {
652+
NL_SET_ERR_MSG_MOD(extack,
653+
"Flush device does not match target port");
654+
return -EOPNOTSUPP;
655+
}
656+
657+
if (tb[NDA_VLAN])
658+
vid = nla_get_u16(tb[NDA_VLAN]);
659+
660+
return dsa_port_flush_dynamic_fdb(dp, vid);
661+
}
662+
578663
static int dsa_user_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
579664
{
580665
struct dsa_user_priv *p = netdev_priv(dev);
@@ -2704,6 +2789,7 @@ static const struct net_device_ops dsa_user_netdev_ops = {
27042789
.ndo_set_rx_mode = dsa_user_set_rx_mode,
27052790
.ndo_set_mac_address = dsa_user_set_mac_address,
27062791
.ndo_fdb_dump = dsa_user_fdb_dump,
2792+
.ndo_fdb_del_bulk = dsa_user_fdb_del_bulk,
27072793
.ndo_eth_ioctl = dsa_user_ioctl,
27082794
.ndo_get_iflink = dsa_user_get_iflink,
27092795
#ifdef CONFIG_NET_POLL_CONTROLLER

0 commit comments

Comments
 (0)