Commit 64fdc5f3 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by Jakub Kicinski

net: dsa: sync unicast and multicast addresses for VLAN filters too

If certain conditions are met, DSA can install all necessary MAC
addresses on the CPU ports as FDB entries and disable flooding towards
the CPU (we call this RX filtering).

There is one corner case where this does not work.

ip link add br0 type bridge vlan_filtering 1 && ip link set br0 up
ip link set swp0 master br0 && ip link set swp0 up
ip link add link swp0 name swp0.100 type vlan id 100
ip link set swp0.100 up && ip addr add 192.168.100.1/24 dev swp0.100

Traffic through swp0.100 is broken, because the bridge turns on VLAN
filtering in the swp0 port (causing RX packets to be classified to the
FDB database corresponding to the VID from their 802.1Q header), and
although the 8021q module does call dev_uc_add() towards the real
device, that API is VLAN-unaware, so it only contains the MAC address,
not the VID; and DSA's current implementation of ndo_set_rx_mode() is
only for VID 0 (corresponding to FDB entries which are installed in an
FDB database which is only hit when the port is VLAN-unaware).

It's interesting to understand why the bridge does not turn on
IFF_PROMISC for its swp0 bridge port, and it may appear at first glance
that this is a regression caused by the logic in commit 2796d0c6
("bridge: Automatically manage port promiscuous mode."). After all,
a bridge port needs to have IFF_PROMISC by its very nature - it needs to
receive and forward frames with a MAC DA different from the bridge
ports' MAC addresses.

While that may be true, when the bridge is VLAN-aware *and* it has a
single port, there is no real reason to enable promiscuity even if that
is an automatic port, with flooding and learning (there is nowhere for
packets to go except to the BR_FDB_LOCAL entries), and this is how the
corner case appears. Adding a second automatic interface to the bridge
would make swp0 promisc as well, and would mask the corner case.

Given the dev_uc_add() / ndo_set_rx_mode() API is what it is (it doesn't
pass a VLAN ID), the only way to address that problem is to install host
FDB entries for the cartesian product of RX filtering MAC addresses and
VLAN RX filters.

Fixes: 7569459a ("net: dsa: manage flooding on the CPU ports")
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: default avatarSimon Horman <simon.horman@corigine.com>
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Link: https://lore.kernel.org/r/20230329151821.745752-1-vladimir.oltean@nxp.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 7bcad0f0
...@@ -57,6 +57,12 @@ struct dsa_standalone_event_work { ...@@ -57,6 +57,12 @@ struct dsa_standalone_event_work {
u16 vid; u16 vid;
}; };
struct dsa_host_vlan_rx_filtering_ctx {
struct net_device *dev;
const unsigned char *addr;
enum dsa_standalone_event event;
};
static bool dsa_switch_supports_uc_filtering(struct dsa_switch *ds) static bool dsa_switch_supports_uc_filtering(struct dsa_switch *ds)
{ {
return ds->ops->port_fdb_add && ds->ops->port_fdb_del && return ds->ops->port_fdb_add && ds->ops->port_fdb_del &&
...@@ -155,18 +161,37 @@ static int dsa_slave_schedule_standalone_work(struct net_device *dev, ...@@ -155,18 +161,37 @@ static int dsa_slave_schedule_standalone_work(struct net_device *dev,
return 0; return 0;
} }
static int dsa_slave_host_vlan_rx_filtering(struct net_device *vdev, int vid,
void *arg)
{
struct dsa_host_vlan_rx_filtering_ctx *ctx = arg;
return dsa_slave_schedule_standalone_work(ctx->dev, ctx->event,
ctx->addr, vid);
}
static int dsa_slave_sync_uc(struct net_device *dev, static int dsa_slave_sync_uc(struct net_device *dev,
const unsigned char *addr) const unsigned char *addr)
{ {
struct net_device *master = dsa_slave_to_master(dev); struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct dsa_host_vlan_rx_filtering_ctx ctx = {
.dev = dev,
.addr = addr,
.event = DSA_UC_ADD,
};
int err;
dev_uc_add(master, addr); dev_uc_add(master, addr);
if (!dsa_switch_supports_uc_filtering(dp->ds)) if (!dsa_switch_supports_uc_filtering(dp->ds))
return 0; return 0;
return dsa_slave_schedule_standalone_work(dev, DSA_UC_ADD, addr, 0); err = dsa_slave_schedule_standalone_work(dev, DSA_UC_ADD, addr, 0);
if (err)
return err;
return vlan_for_each(dev, dsa_slave_host_vlan_rx_filtering, &ctx);
} }
static int dsa_slave_unsync_uc(struct net_device *dev, static int dsa_slave_unsync_uc(struct net_device *dev,
...@@ -174,13 +199,23 @@ static int dsa_slave_unsync_uc(struct net_device *dev, ...@@ -174,13 +199,23 @@ static int dsa_slave_unsync_uc(struct net_device *dev,
{ {
struct net_device *master = dsa_slave_to_master(dev); struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct dsa_host_vlan_rx_filtering_ctx ctx = {
.dev = dev,
.addr = addr,
.event = DSA_UC_DEL,
};
int err;
dev_uc_del(master, addr); dev_uc_del(master, addr);
if (!dsa_switch_supports_uc_filtering(dp->ds)) if (!dsa_switch_supports_uc_filtering(dp->ds))
return 0; return 0;
return dsa_slave_schedule_standalone_work(dev, DSA_UC_DEL, addr, 0); err = dsa_slave_schedule_standalone_work(dev, DSA_UC_DEL, addr, 0);
if (err)
return err;
return vlan_for_each(dev, dsa_slave_host_vlan_rx_filtering, &ctx);
} }
static int dsa_slave_sync_mc(struct net_device *dev, static int dsa_slave_sync_mc(struct net_device *dev,
...@@ -188,13 +223,23 @@ static int dsa_slave_sync_mc(struct net_device *dev, ...@@ -188,13 +223,23 @@ static int dsa_slave_sync_mc(struct net_device *dev,
{ {
struct net_device *master = dsa_slave_to_master(dev); struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct dsa_host_vlan_rx_filtering_ctx ctx = {
.dev = dev,
.addr = addr,
.event = DSA_MC_ADD,
};
int err;
dev_mc_add(master, addr); dev_mc_add(master, addr);
if (!dsa_switch_supports_mc_filtering(dp->ds)) if (!dsa_switch_supports_mc_filtering(dp->ds))
return 0; return 0;
return dsa_slave_schedule_standalone_work(dev, DSA_MC_ADD, addr, 0); err = dsa_slave_schedule_standalone_work(dev, DSA_MC_ADD, addr, 0);
if (err)
return err;
return vlan_for_each(dev, dsa_slave_host_vlan_rx_filtering, &ctx);
} }
static int dsa_slave_unsync_mc(struct net_device *dev, static int dsa_slave_unsync_mc(struct net_device *dev,
...@@ -202,13 +247,23 @@ static int dsa_slave_unsync_mc(struct net_device *dev, ...@@ -202,13 +247,23 @@ static int dsa_slave_unsync_mc(struct net_device *dev,
{ {
struct net_device *master = dsa_slave_to_master(dev); struct net_device *master = dsa_slave_to_master(dev);
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct dsa_host_vlan_rx_filtering_ctx ctx = {
.dev = dev,
.addr = addr,
.event = DSA_MC_DEL,
};
int err;
dev_mc_del(master, addr); dev_mc_del(master, addr);
if (!dsa_switch_supports_mc_filtering(dp->ds)) if (!dsa_switch_supports_mc_filtering(dp->ds))
return 0; return 0;
return dsa_slave_schedule_standalone_work(dev, DSA_MC_DEL, addr, 0); err = dsa_slave_schedule_standalone_work(dev, DSA_MC_DEL, addr, 0);
if (err)
return err;
return vlan_for_each(dev, dsa_slave_host_vlan_rx_filtering, &ctx);
} }
void dsa_slave_sync_ha(struct net_device *dev) void dsa_slave_sync_ha(struct net_device *dev)
...@@ -1702,6 +1757,8 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto, ...@@ -1702,6 +1757,8 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
.flags = 0, .flags = 0,
}; };
struct netlink_ext_ack extack = {0}; struct netlink_ext_ack extack = {0};
struct dsa_switch *ds = dp->ds;
struct netdev_hw_addr *ha;
int ret; int ret;
/* User port... */ /* User port... */
...@@ -1721,6 +1778,30 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto, ...@@ -1721,6 +1778,30 @@ static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto,
return ret; return ret;
} }
if (!dsa_switch_supports_uc_filtering(ds) &&
!dsa_switch_supports_mc_filtering(ds))
return 0;
netif_addr_lock_bh(dev);
if (dsa_switch_supports_mc_filtering(ds)) {
netdev_for_each_synced_mc_addr(ha, dev) {
dsa_slave_schedule_standalone_work(dev, DSA_MC_ADD,
ha->addr, vid);
}
}
if (dsa_switch_supports_uc_filtering(ds)) {
netdev_for_each_synced_uc_addr(ha, dev) {
dsa_slave_schedule_standalone_work(dev, DSA_UC_ADD,
ha->addr, vid);
}
}
netif_addr_unlock_bh(dev);
dsa_flush_workqueue();
return 0; return 0;
} }
...@@ -1733,13 +1814,43 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, ...@@ -1733,13 +1814,43 @@ static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto,
/* This API only allows programming tagged, non-PVID VIDs */ /* This API only allows programming tagged, non-PVID VIDs */
.flags = 0, .flags = 0,
}; };
struct dsa_switch *ds = dp->ds;
struct netdev_hw_addr *ha;
int err; int err;
err = dsa_port_vlan_del(dp, &vlan); err = dsa_port_vlan_del(dp, &vlan);
if (err) if (err)
return err; return err;
return dsa_port_host_vlan_del(dp, &vlan); err = dsa_port_host_vlan_del(dp, &vlan);
if (err)
return err;
if (!dsa_switch_supports_uc_filtering(ds) &&
!dsa_switch_supports_mc_filtering(ds))
return 0;
netif_addr_lock_bh(dev);
if (dsa_switch_supports_mc_filtering(ds)) {
netdev_for_each_synced_mc_addr(ha, dev) {
dsa_slave_schedule_standalone_work(dev, DSA_MC_DEL,
ha->addr, vid);
}
}
if (dsa_switch_supports_uc_filtering(ds)) {
netdev_for_each_synced_uc_addr(ha, dev) {
dsa_slave_schedule_standalone_work(dev, DSA_UC_DEL,
ha->addr, vid);
}
}
netif_addr_unlock_bh(dev);
dsa_flush_workqueue();
return 0;
} }
static int dsa_slave_restore_vlan(struct net_device *vdev, int vid, void *arg) static int dsa_slave_restore_vlan(struct net_device *vdev, int vid, void *arg)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment