Commit 04846f90 authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: bridge: add helper to replay port and local fdb entries

When a switchdev port starts offloading a LAG that is already in a
bridge and has an FDB entry pointing to it:

ip link set bond0 master br0
bridge fdb add dev bond0 00:01:02:03:04:05 master static
ip link set swp0 master bond0

the switchdev driver will have no idea that this FDB entry is there,
because it missed the switchdev event emitted at its creation.

Ido Schimmel pointed this out during a discussion about challenges with
switchdev offloading of stacked interfaces between the physical port and
the bridge, and recommended to just catch that condition and deny the
CHANGEUPPER event:
https://lore.kernel.org/netdev/20210210105949.GB287766@shredder.lan/

But in fact, we might need to deal with the hard thing anyway, which is
to replay all FDB addresses relevant to this port, because it isn't just
static FDB entries, but also local addresses (ones that are not
forwarded but terminated by the bridge). There, we can't just say 'oh
yeah, there was an upper already so I'm not joining that'.

So, similar to the logic for replaying MDB entries, add a function that
must be called by individual switchdev drivers and replays local FDB
entries as well as ones pointing towards a bridge port. This time, we
use the atomic switchdev notifier block, since that's what FDB entries
expect for some reason.
Reported-by: default avatarIdo Schimmel <idosch@idosch.org>
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Acked-by: default avatarNikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 4f2673b3
...@@ -147,6 +147,8 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid); ...@@ -147,6 +147,8 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid);
bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag); bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag);
u8 br_port_get_stp_state(const struct net_device *dev); u8 br_port_get_stp_state(const struct net_device *dev);
clock_t br_get_ageing_time(struct net_device *br_dev); clock_t br_get_ageing_time(struct net_device *br_dev);
int br_fdb_replay(struct net_device *br_dev, struct net_device *dev,
struct notifier_block *nb);
#else #else
static inline struct net_device * static inline struct net_device *
br_fdb_find_port(const struct net_device *br_dev, br_fdb_find_port(const struct net_device *br_dev,
...@@ -175,6 +177,13 @@ static inline clock_t br_get_ageing_time(struct net_device *br_dev) ...@@ -175,6 +177,13 @@ static inline clock_t br_get_ageing_time(struct net_device *br_dev)
{ {
return 0; return 0;
} }
static inline int br_fdb_replay(struct net_device *br_dev,
struct net_device *dev,
struct notifier_block *nb)
{
return -EOPNOTSUPP;
}
#endif #endif
#endif #endif
...@@ -726,6 +726,56 @@ static inline size_t fdb_nlmsg_size(void) ...@@ -726,6 +726,56 @@ static inline size_t fdb_nlmsg_size(void)
+ nla_total_size(sizeof(u8)); /* NFEA_ACTIVITY_NOTIFY */ + nla_total_size(sizeof(u8)); /* NFEA_ACTIVITY_NOTIFY */
} }
static int br_fdb_replay_one(struct notifier_block *nb,
struct net_bridge_fdb_entry *fdb,
struct net_device *dev)
{
struct switchdev_notifier_fdb_info item;
int err;
item.addr = fdb->key.addr.addr;
item.vid = fdb->key.vlan_id;
item.added_by_user = test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
item.offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags);
item.info.dev = dev;
err = nb->notifier_call(nb, SWITCHDEV_FDB_ADD_TO_DEVICE, &item);
return notifier_to_errno(err);
}
int br_fdb_replay(struct net_device *br_dev, struct net_device *dev,
struct notifier_block *nb)
{
struct net_bridge_fdb_entry *fdb;
struct net_bridge *br;
int err = 0;
if (!netif_is_bridge_master(br_dev) || !netif_is_bridge_port(dev))
return -EINVAL;
br = netdev_priv(br_dev);
rcu_read_lock();
hlist_for_each_entry_rcu(fdb, &br->fdb_list, fdb_node) {
struct net_bridge_port *dst = READ_ONCE(fdb->dst);
struct net_device *dst_dev;
dst_dev = dst ? dst->dev : br->dev;
if (dst_dev != br_dev && dst_dev != dev)
continue;
err = br_fdb_replay_one(nb, fdb, dst_dev);
if (err)
break;
}
rcu_read_unlock();
return err;
}
EXPORT_SYMBOL_GPL(br_fdb_replay);
static void fdb_notify(struct net_bridge *br, static void fdb_notify(struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb, int type, const struct net_bridge_fdb_entry *fdb, int type,
bool swdev_notify) bool swdev_notify)
......
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