Skip to content

Commit

Permalink
Merge branch 'dsa-rx-filtering'
Browse files Browse the repository at this point in the history
Vladimir Oltean says:

====================
RX filtering in DSA

This is my fourth stab (identical to the third one except sent as
non-RFC) at creating a list of unicast and multicast addresses that the
DSA CPU ports must trap. I am reusing a lot of Tobias's work which he
submitted here:
https://patchwork.kernel.org/project/netdevbpf/cover/[email protected]/

My additions to Tobias' work come in the form of taking some care that
additions and removals of host addresses are properly balanced, so that
we can do reference counting on them for cross-chip setups and multiple
bridges spanning the same switch (I am working on an NXP board where
both are real requirements).

During the last attempted submission of multiple CPU ports for DSA:
https://patchwork.kernel.org/project/netdevbpf/cover/[email protected]/

it became clear that the concept of multiple CPU ports would not be
compatible with the idea of address learning on those CPU ports (when
those CPU ports are statically assigned to user ports, not in a LAG)
unless the switch supports complete FDB isolation, which most switches
do not. So DSA needs to manage in software all addresses that are
installed on the CPU port(s), which is what this patch set does.

Compared to all earlier attempts, this series does not fiddle with how
DSA operates the ports in standalone mode at all, just when bridged.
We need to sort that out properly, then any optimization that comes in
standalone mode (i.e. IFF_UNICAST_FLT) can come later.
====================

Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
davem330 committed Jun 29, 2021
2 parents 84fe739 + 63c5145 commit 7f4e5c5
Show file tree
Hide file tree
Showing 10 changed files with 573 additions and 79 deletions.
68 changes: 68 additions & 0 deletions Documentation/networking/dsa/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,3 +292,71 @@ configuration.
# bring up the bridge devices
ip link set br0 up
Forwarding database (FDB) management
------------------------------------

The existing DSA switches do not have the necessary hardware support to keep
the software FDB of the bridge in sync with the hardware tables, so the two
tables are managed separately (``bridge fdb show`` queries both, and depending
on whether the ``self`` or ``master`` flags are being used, a ``bridge fdb
add`` or ``bridge fdb del`` command acts upon entries from one or both tables).

Up until kernel v4.14, DSA only supported user space management of bridge FDB
entries using the bridge bypass operations (which do not update the software
FDB, just the hardware one) using the ``self`` flag (which is optional and can
be omitted).

.. code-block:: sh
bridge fdb add dev swp0 00:01:02:03:04:05 self static
# or shorthand
bridge fdb add dev swp0 00:01:02:03:04:05 static
Due to a bug, the bridge bypass FDB implementation provided by DSA did not
distinguish between ``static`` and ``local`` FDB entries (``static`` are meant
to be forwarded, while ``local`` are meant to be locally terminated, i.e. sent
to the host port). Instead, all FDB entries with the ``self`` flag (implicit or
explicit) are treated by DSA as ``static`` even if they are ``local``.

.. code-block:: sh
# This command:
bridge fdb add dev swp0 00:01:02:03:04:05 static
# behaves the same for DSA as this command:
bridge fdb add dev swp0 00:01:02:03:04:05 local
# or shorthand, because the 'local' flag is implicit if 'static' is not
# specified, it also behaves the same as:
bridge fdb add dev swp0 00:01:02:03:04:05
The last command is an incorrect way of adding a static bridge FDB entry to a
DSA switch using the bridge bypass operations, and works by mistake. Other
drivers will treat an FDB entry added by the same command as ``local`` and as
such, will not forward it, as opposed to DSA.

Between kernel v4.14 and v5.14, DSA has supported in parallel two modes of
adding a bridge FDB entry to the switch: the bridge bypass discussed above, as
well as a new mode using the ``master`` flag which installs FDB entries in the
software bridge too.

.. code-block:: sh
bridge fdb add dev swp0 00:01:02:03:04:05 master static
Since kernel v5.14, DSA has gained stronger integration with the bridge's
software FDB, and the support for its bridge bypass FDB implementation (using
the ``self`` flag) has been removed. This results in the following changes:

.. code-block:: sh
# This is the only valid way of adding an FDB entry that is supported,
# compatible with v4.14 kernels and later:
bridge fdb add dev swp0 00:01:02:03:04:05 master static
# This command is no longer buggy and the entry is properly treated as
# 'local' instead of being forwarded:
bridge fdb add dev swp0 00:01:02:03:04:05
# This command no longer installs a static FDB entry to hardware:
bridge fdb add dev swp0 00:01:02:03:04:05 static
Script writers are therefore encouraged to use the ``master static`` set of
flags when working with bridge FDB entries on DSA switch interfaces.
39 changes: 39 additions & 0 deletions include/net/dsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,12 @@ struct dsa_port {
*/
const struct dsa_netdevice_ops *netdev_ops;

/* List of MAC addresses that must be forwarded on this port.
* These are only valid on CPU ports and DSA links.
*/
struct list_head fdbs;
struct list_head mdbs;

bool setup;
};

Expand All @@ -299,6 +305,13 @@ struct dsa_link {
struct list_head list;
};

struct dsa_mac_addr {
unsigned char addr[ETH_ALEN];
u16 vid;
refcount_t refcount;
struct list_head list;
};

struct dsa_switch {
bool setup;

Expand Down Expand Up @@ -491,6 +504,32 @@ static inline unsigned int dsa_upstream_port(struct dsa_switch *ds, int port)
return dsa_towards_port(ds, cpu_dp->ds->index, cpu_dp->index);
}

/* Return true if this is the local port used to reach the CPU port */
static inline bool dsa_is_upstream_port(struct dsa_switch *ds, int port)
{
if (dsa_is_unused_port(ds, port))
return false;

return port == dsa_upstream_port(ds, port);
}

/* Return true if @upstream_ds is an upstream switch of @downstream_ds, meaning
* that the routing port from @downstream_ds to @upstream_ds is also the port
* which @downstream_ds uses to reach its dedicated CPU.
*/
static inline bool dsa_switch_is_upstream_of(struct dsa_switch *upstream_ds,
struct dsa_switch *downstream_ds)
{
int routing_port;

if (upstream_ds == downstream_ds)
return true;

routing_port = dsa_routing_port(downstream_ds, upstream_ds->index);

return dsa_is_upstream_port(downstream_ds, routing_port);
}

static inline bool dsa_port_is_vlan_filtering(const struct dsa_port *dp)
{
const struct dsa_switch *ds = dp->ds;
Expand Down
37 changes: 23 additions & 14 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -440,9 +440,14 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr)
if (!port)
ret = 0;
else {
const struct net_bridge_port *dst = NULL;

fdb = br_fdb_find_rcu(port->br, addr, 0);
ret = fdb && fdb->dst && fdb->dst->dev != dev &&
fdb->dst->state == BR_STATE_FORWARDING;
if (fdb)
dst = READ_ONCE(fdb->dst);

ret = dst && dst->dev != dev &&
dst->state == BR_STATE_FORWARDING;
}
rcu_read_unlock();

Expand Down Expand Up @@ -509,7 +514,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br,
fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC);
if (fdb) {
memcpy(fdb->key.addr.addr, addr, ETH_ALEN);
fdb->dst = source;
WRITE_ONCE(fdb->dst, source);
fdb->key.vlan_id = vid;
fdb->flags = flags;
fdb->updated = fdb->used = jiffies;
Expand Down Expand Up @@ -600,10 +605,10 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
}

/* fastpath: update of existing entry */
if (unlikely(source != fdb->dst &&
if (unlikely(source != READ_ONCE(fdb->dst) &&
!test_bit(BR_FDB_STICKY, &fdb->flags))) {
br_switchdev_fdb_notify(fdb, RTM_DELNEIGH);
fdb->dst = source;
br_switchdev_fdb_notify(br, fdb, RTM_DELNEIGH);
WRITE_ONCE(fdb->dst, source);
fdb_modified = true;
/* Take over HW learned entry */
if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN,
Expand Down Expand Up @@ -650,6 +655,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb,
u32 portid, u32 seq, int type, unsigned int flags)
{
const struct net_bridge_port *dst = READ_ONCE(fdb->dst);
unsigned long now = jiffies;
struct nda_cacheinfo ci;
struct nlmsghdr *nlh;
Expand All @@ -665,7 +671,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
ndm->ndm_pad2 = 0;
ndm->ndm_flags = 0;
ndm->ndm_type = 0;
ndm->ndm_ifindex = fdb->dst ? fdb->dst->dev->ifindex : br->dev->ifindex;
ndm->ndm_ifindex = dst ? dst->dev->ifindex : br->dev->ifindex;
ndm->ndm_state = fdb_to_nud(br, fdb);

if (test_bit(BR_FDB_OFFLOADED, &fdb->flags))
Expand Down Expand Up @@ -754,7 +760,10 @@ int br_fdb_replay(const struct net_device *br_dev, const struct net_device *dev,
unsigned long action;
int err = 0;

if (!netif_is_bridge_master(br_dev) || !netif_is_bridge_port(dev))
if (!netif_is_bridge_master(br_dev))
return -EINVAL;

if (!netif_is_bridge_port(dev) && !netif_is_bridge_master(dev))
return -EINVAL;

br = netdev_priv(br_dev);
Expand Down Expand Up @@ -794,7 +803,7 @@ static void fdb_notify(struct net_bridge *br,
int err = -ENOBUFS;

if (swdev_notify)
br_switchdev_fdb_notify(fdb, type);
br_switchdev_fdb_notify(br, fdb, type);

skb = nlmsg_new(fdb_nlmsg_size(), GFP_ATOMIC);
if (skb == NULL)
Expand Down Expand Up @@ -964,8 +973,8 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
if (flags & NLM_F_EXCL)
return -EEXIST;

if (fdb->dst != source) {
fdb->dst = source;
if (READ_ONCE(fdb->dst) != source) {
WRITE_ONCE(fdb->dst, source);
modified = true;
}
}
Expand Down Expand Up @@ -1132,7 +1141,7 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br,
struct net_bridge_fdb_entry *fdb;

fdb = br_fdb_find(br, addr, vlan);
if (!fdb || fdb->dst != p)
if (!fdb || READ_ONCE(fdb->dst) != p)
return -ENOENT;

fdb_delete(br, fdb, true);
Expand Down Expand Up @@ -1281,8 +1290,8 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p,
} else {
fdb->updated = jiffies;

if (fdb->dst != p) {
fdb->dst = p;
if (READ_ONCE(fdb->dst) != p) {
WRITE_ONCE(fdb->dst, p);
modified = true;
}

Expand Down
7 changes: 4 additions & 3 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -1654,8 +1654,8 @@ int br_switchdev_set_port_flag(struct net_bridge_port *p,
unsigned long flags,
unsigned long mask,
struct netlink_ext_ack *extack);
void br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb,
int type);
void br_switchdev_fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb, int type);
int br_switchdev_port_vlan_add(struct net_device *dev, u16 vid, u16 flags,
struct netlink_ext_ack *extack);
int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid);
Expand Down Expand Up @@ -1702,7 +1702,8 @@ static inline int br_switchdev_port_vlan_del(struct net_device *dev, u16 vid)
}

static inline void
br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
br_switchdev_fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb, int type)
{
}

Expand Down
12 changes: 6 additions & 6 deletions net/bridge/br_switchdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,11 @@ int br_switchdev_set_port_flag(struct net_bridge_port *p,
}

void
br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
br_switchdev_fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb, int type)
{
const struct net_bridge_port *dst = READ_ONCE(fdb->dst);
struct net_device *dev = dst ? dst->dev : br->dev;
struct switchdev_notifier_fdb_info info = {
.addr = fdb->key.addr.addr,
.vid = fdb->key.vlan_id,
Expand All @@ -118,17 +121,14 @@ br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type)
.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags),
};

if (!fdb->dst)
return;

switch (type) {
case RTM_DELNEIGH:
call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
fdb->dst->dev, &info.info, NULL);
dev, &info.info, NULL);
break;
case RTM_NEWNEIGH:
call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
fdb->dst->dev, &info.info, NULL);
dev, &info.info, NULL);
break;
}
}
Expand Down
14 changes: 14 additions & 0 deletions net/dsa/dsa2.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ static int dsa_port_setup(struct dsa_port *dp)
if (dp->setup)
return 0;

INIT_LIST_HEAD(&dp->fdbs);
INIT_LIST_HEAD(&dp->mdbs);

switch (dp->type) {
case DSA_PORT_TYPE_UNUSED:
dsa_port_disable(dp);
Expand Down Expand Up @@ -443,6 +446,7 @@ static int dsa_port_devlink_setup(struct dsa_port *dp)
static void dsa_port_teardown(struct dsa_port *dp)
{
struct devlink_port *dlp = &dp->devlink_port;
struct dsa_mac_addr *a, *tmp;

if (!dp->setup)
return;
Expand All @@ -468,6 +472,16 @@ static void dsa_port_teardown(struct dsa_port *dp)
break;
}

list_for_each_entry_safe(a, tmp, &dp->fdbs, list) {
list_del(&a->list);
kfree(a);
}

list_for_each_entry_safe(a, tmp, &dp->mdbs, list) {
list_del(&a->list);
kfree(a);
}

dp->setup = false;
}

Expand Down
14 changes: 14 additions & 0 deletions net/dsa/dsa_priv.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ enum {
DSA_NOTIFIER_BRIDGE_LEAVE,
DSA_NOTIFIER_FDB_ADD,
DSA_NOTIFIER_FDB_DEL,
DSA_NOTIFIER_HOST_FDB_ADD,
DSA_NOTIFIER_HOST_FDB_DEL,
DSA_NOTIFIER_HSR_JOIN,
DSA_NOTIFIER_HSR_LEAVE,
DSA_NOTIFIER_LAG_CHANGE,
DSA_NOTIFIER_LAG_JOIN,
DSA_NOTIFIER_LAG_LEAVE,
DSA_NOTIFIER_MDB_ADD,
DSA_NOTIFIER_MDB_DEL,
DSA_NOTIFIER_HOST_MDB_ADD,
DSA_NOTIFIER_HOST_MDB_DEL,
DSA_NOTIFIER_VLAN_ADD,
DSA_NOTIFIER_VLAN_DEL,
DSA_NOTIFIER_MTU,
Expand Down Expand Up @@ -112,13 +116,15 @@ struct dsa_notifier_mrp_ring_role_info {
struct dsa_switchdev_event_work {
struct dsa_switch *ds;
int port;
struct net_device *dev;
struct work_struct work;
unsigned long event;
/* Specific for SWITCHDEV_FDB_ADD_TO_DEVICE and
* SWITCHDEV_FDB_DEL_TO_DEVICE
*/
unsigned char addr[ETH_ALEN];
u16 vid;
bool host_addr;
};

/* DSA_NOTIFIER_HSR_* */
Expand Down Expand Up @@ -209,11 +215,19 @@ int dsa_port_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_fdb_del(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_host_fdb_add(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_host_fdb_del(struct dsa_port *dp, const unsigned char *addr,
u16 vid);
int dsa_port_fdb_dump(struct dsa_port *dp, dsa_fdb_dump_cb_t *cb, void *data);
int dsa_port_mdb_add(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
int dsa_port_mdb_del(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
int dsa_port_host_mdb_add(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
int dsa_port_host_mdb_del(const struct dsa_port *dp,
const struct switchdev_obj_port_mdb *mdb);
int dsa_port_pre_bridge_flags(const struct dsa_port *dp,
struct switchdev_brport_flags flags,
struct netlink_ext_ack *extack);
Expand Down
Loading

0 comments on commit 7f4e5c5

Please sign in to comment.