Commit 083cd5a4 authored by David S. Miller's avatar David S. Miller

Merge branch 'fdb-fanout'

Vladimir Oltean says:

====================
Fan out FDB entries pointing towards the bridge to all switchdev member ports

The "DSA RX filtering" series has added some important support for
interpreting addresses towards the bridge device as host addresses and
installing them as FDB entries towards the CPU port, but it does not
cover all circumstances and needs further work.

To be precise, the mechanism introduced in that series only works as
long as the ports are fairly static and no port joins or leaves the
bridge once the configuration is done. If any port leaves, host FDB
entries that were installed during runtime (for example the user changes
the MAC address of the bridge device) will be prematurely deleted,
resulting in a broken setup.

I see this work as targeted for "net-next" because technically it was
not supposed to work. Also, there are still corner cases and holes to be
plugged. For example, today, FDB entries on foreign interfaces are not
covered by br_fdb_replay(), which means that there are cases where some
host addresses are either lost, or never deleted by DSA. That will be
resolved once more work gets accepted, in particular the "Allow
forwarding for the software bridge data path to be offloaded to capable
devices" series, which moves the br_fdb_replay() call to the bridge core
and therefore would be required to solve the problem in a generic way
for every switchdev driver and not just for DSA.

These patches also pave the way for a cleaner implementation for FDB
entries pointing towards a LAG upper interface in DSA (that code needs
only to be added, nothing changed), however this is not done here.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 7d901a1e b94dc99c
...@@ -238,6 +238,12 @@ switchdev_notifier_info_to_extack(const struct switchdev_notifier_info *info) ...@@ -238,6 +238,12 @@ switchdev_notifier_info_to_extack(const struct switchdev_notifier_info *info)
return info->extack; return info->extack;
} }
static inline bool
switchdev_fdb_is_dynamically_learned(const struct switchdev_notifier_fdb_info *fdb_info)
{
return !fdb_info->added_by_user && !fdb_info->is_local;
}
#ifdef CONFIG_NET_SWITCHDEV #ifdef CONFIG_NET_SWITCHDEV
void switchdev_deferred_process(void); void switchdev_deferred_process(void);
...@@ -266,6 +272,30 @@ void switchdev_port_fwd_mark_set(struct net_device *dev, ...@@ -266,6 +272,30 @@ void switchdev_port_fwd_mark_set(struct net_device *dev,
struct net_device *group_dev, struct net_device *group_dev,
bool joining); bool joining);
int switchdev_handle_fdb_add_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info));
int switchdev_handle_fdb_del_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info));
int switchdev_handle_port_obj_add(struct net_device *dev, int switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info, struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev), bool (*check_cb)(const struct net_device *dev),
...@@ -349,6 +379,38 @@ call_switchdev_blocking_notifiers(unsigned long val, ...@@ -349,6 +379,38 @@ call_switchdev_blocking_notifiers(unsigned long val,
return NOTIFY_DONE; return NOTIFY_DONE;
} }
static inline int
switchdev_handle_fdb_add_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info))
{
return 0;
}
static inline int
switchdev_handle_fdb_del_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info));
{
return 0;
}
static inline int static inline int
switchdev_handle_port_obj_add(struct net_device *dev, switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info, struct switchdev_notifier_port_obj_info *port_obj_info,
......
...@@ -268,13 +268,13 @@ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid); ...@@ -268,13 +268,13 @@ void dsa_port_tag_8021q_vlan_del(struct dsa_port *dp, u16 vid);
extern const struct phylink_mac_ops dsa_port_phylink_mac_ops; extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp, static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
struct net_device *dev) const struct net_device *dev)
{ {
return dsa_port_to_bridge_port(dp) == dev; return dsa_port_to_bridge_port(dp) == dev;
} }
static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
struct net_device *bridge_dev) const struct net_device *bridge_dev)
{ {
/* DSA ports connected to a bridge, and event was emitted /* DSA ports connected to a bridge, and event was emitted
* for the bridge. * for the bridge.
...@@ -284,7 +284,7 @@ static inline bool dsa_port_offloads_bridge(struct dsa_port *dp, ...@@ -284,7 +284,7 @@ static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
/* Returns true if any port of this tree offloads the given net_device */ /* Returns true if any port of this tree offloads the given net_device */
static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
struct net_device *dev) const struct net_device *dev)
{ {
struct dsa_port *dp; struct dsa_port *dp;
...@@ -295,6 +295,19 @@ static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst, ...@@ -295,6 +295,19 @@ static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
return false; return false;
} }
/* Returns true if any port of this tree offloads the given bridge */
static inline bool dsa_tree_offloads_bridge(struct dsa_switch_tree *dst,
const struct net_device *bridge_dev)
{
struct dsa_port *dp;
list_for_each_entry(dp, &dst->ports, list)
if (dsa_port_offloads_bridge(dp, bridge_dev))
return true;
return false;
}
/* slave.c */ /* slave.c */
extern const struct dsa_device_ops notag_netdev_ops; extern const struct dsa_device_ops notag_netdev_ops;
extern struct notifier_block dsa_slave_switchdev_notifier; extern struct notifier_block dsa_slave_switchdev_notifier;
......
...@@ -2353,26 +2353,98 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work) ...@@ -2353,26 +2353,98 @@ static void dsa_slave_switchdev_event_work(struct work_struct *work)
kfree(switchdev_work); kfree(switchdev_work);
} }
static int dsa_lower_dev_walk(struct net_device *lower_dev, static bool dsa_foreign_dev_check(const struct net_device *dev,
struct netdev_nested_priv *priv) const struct net_device *foreign_dev)
{ {
if (dsa_slave_dev_check(lower_dev)) { const struct dsa_port *dp = dsa_slave_to_port(dev);
priv->data = (void *)netdev_priv(lower_dev); struct dsa_switch_tree *dst = dp->ds->dst;
return 1;
}
return 0; if (netif_is_bridge_master(foreign_dev))
return !dsa_tree_offloads_bridge(dst, foreign_dev);
if (netif_is_bridge_port(foreign_dev))
return !dsa_tree_offloads_bridge_port(dst, foreign_dev);
/* Everything else is foreign */
return true;
} }
static struct dsa_slave_priv *dsa_slave_dev_lower_find(struct net_device *dev) static int dsa_slave_fdb_event(struct net_device *dev,
const struct net_device *orig_dev,
const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info,
unsigned long event)
{ {
struct netdev_nested_priv priv = { struct dsa_switchdev_event_work *switchdev_work;
.data = NULL, struct dsa_port *dp = dsa_slave_to_port(dev);
}; bool host_addr = fdb_info->is_local;
struct dsa_switch *ds = dp->ds;
if (ctx && ctx != dp)
return 0;
if (!ds->ops->port_fdb_add || !ds->ops->port_fdb_del)
return -EOPNOTSUPP;
if (dsa_slave_dev_check(orig_dev) &&
switchdev_fdb_is_dynamically_learned(fdb_info))
return 0;
/* FDB entries learned by the software bridge should be installed as
* host addresses only if the driver requests assisted learning.
*/
if (switchdev_fdb_is_dynamically_learned(fdb_info) &&
!ds->assisted_learning_on_cpu_port)
return 0;
/* Also treat FDB entries on foreign interfaces bridged with us as host
* addresses.
*/
if (dsa_foreign_dev_check(dev, orig_dev))
host_addr = true;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
if (!switchdev_work)
return -ENOMEM;
netdev_dbg(dev, "%s FDB entry towards %s, addr %pM vid %d%s\n",
event == SWITCHDEV_FDB_ADD_TO_DEVICE ? "Adding" : "Deleting",
orig_dev->name, fdb_info->addr, fdb_info->vid,
host_addr ? " as host address" : "");
netdev_walk_all_lower_dev_rcu(dev, dsa_lower_dev_walk, &priv); INIT_WORK(&switchdev_work->work, dsa_slave_switchdev_event_work);
switchdev_work->ds = ds;
switchdev_work->port = dp->index;
switchdev_work->event = event;
switchdev_work->dev = dev;
return (struct dsa_slave_priv *)priv.data; ether_addr_copy(switchdev_work->addr, fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
switchdev_work->host_addr = host_addr;
/* Hold a reference for dsa_fdb_offload_notify */
dev_hold(dev);
dsa_schedule_work(&switchdev_work->work);
return 0;
}
static int
dsa_slave_fdb_add_to_device(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
return dsa_slave_fdb_event(dev, orig_dev, ctx, fdb_info,
SWITCHDEV_FDB_ADD_TO_DEVICE);
}
static int
dsa_slave_fdb_del_to_device(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info)
{
return dsa_slave_fdb_event(dev, orig_dev, ctx, fdb_info,
SWITCHDEV_FDB_DEL_TO_DEVICE);
} }
/* Called under rcu_read_lock() */ /* Called under rcu_read_lock() */
...@@ -2380,10 +2452,6 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused, ...@@ -2380,10 +2452,6 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
unsigned long event, void *ptr) unsigned long event, void *ptr)
{ {
struct net_device *dev = switchdev_notifier_info_to_dev(ptr); struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
const struct switchdev_notifier_fdb_info *fdb_info;
struct dsa_switchdev_event_work *switchdev_work;
bool host_addr = false;
struct dsa_port *dp;
int err; int err;
switch (event) { switch (event) {
...@@ -2393,92 +2461,19 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused, ...@@ -2393,92 +2461,19 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
dsa_slave_port_attr_set); dsa_slave_port_attr_set);
return notifier_from_errno(err); return notifier_from_errno(err);
case SWITCHDEV_FDB_ADD_TO_DEVICE: case SWITCHDEV_FDB_ADD_TO_DEVICE:
err = switchdev_handle_fdb_add_to_device(dev, ptr,
dsa_slave_dev_check,
dsa_foreign_dev_check,
dsa_slave_fdb_add_to_device,
NULL);
return notifier_from_errno(err);
case SWITCHDEV_FDB_DEL_TO_DEVICE: case SWITCHDEV_FDB_DEL_TO_DEVICE:
fdb_info = ptr; err = switchdev_handle_fdb_del_to_device(dev, ptr,
dsa_slave_dev_check,
if (dsa_slave_dev_check(dev)) { dsa_foreign_dev_check,
dp = dsa_slave_to_port(dev); dsa_slave_fdb_del_to_device,
NULL);
if (fdb_info->is_local) return notifier_from_errno(err);
host_addr = true;
else if (!fdb_info->added_by_user)
return NOTIFY_OK;
} else {
/* Snoop addresses added to foreign interfaces
* bridged with us, or the bridge
* itself. Dynamically learned addresses can
* also be added for switches that don't
* automatically learn SA from CPU-injected
* traffic.
*/
struct net_device *br_dev;
struct dsa_slave_priv *p;
if (netif_is_bridge_master(dev))
br_dev = dev;
else
br_dev = netdev_master_upper_dev_get_rcu(dev);
if (!br_dev)
return NOTIFY_DONE;
if (!netif_is_bridge_master(br_dev))
return NOTIFY_DONE;
p = dsa_slave_dev_lower_find(br_dev);
if (!p)
return NOTIFY_DONE;
dp = p->dp;
host_addr = fdb_info->is_local;
/* FDB entries learned by the software bridge should
* be installed as host addresses only if the driver
* requests assisted learning.
* On the other hand, FDB entries for local termination
* should always be installed.
*/
if (!fdb_info->added_by_user && !fdb_info->is_local &&
!dp->ds->assisted_learning_on_cpu_port)
return NOTIFY_DONE;
/* When the bridge learns an address on an offloaded
* LAG we don't want to send traffic to the CPU, the
* other ports bridged with the LAG should be able to
* autonomously forward towards it.
* On the other hand, if the address is local
* (therefore not learned) then we want to trap it to
* the CPU regardless of whether the interface it
* belongs to is offloaded or not.
*/
if (dsa_tree_offloads_bridge_port(dp->ds->dst, dev) &&
!fdb_info->is_local)
return NOTIFY_DONE;
}
if (!dp->ds->ops->port_fdb_add || !dp->ds->ops->port_fdb_del)
return NOTIFY_DONE;
switchdev_work = kzalloc(sizeof(*switchdev_work), GFP_ATOMIC);
if (!switchdev_work)
return NOTIFY_BAD;
INIT_WORK(&switchdev_work->work,
dsa_slave_switchdev_event_work);
switchdev_work->ds = dp->ds;
switchdev_work->port = dp->index;
switchdev_work->event = event;
switchdev_work->dev = dev;
ether_addr_copy(switchdev_work->addr,
fdb_info->addr);
switchdev_work->vid = fdb_info->vid;
switchdev_work->host_addr = host_addr;
/* Hold a reference for dsa_fdb_offload_notify */
dev_hold(dev);
dsa_schedule_work(&switchdev_work->work);
break;
default: default:
return NOTIFY_DONE; return NOTIFY_DONE;
} }
......
...@@ -378,6 +378,196 @@ int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev, ...@@ -378,6 +378,196 @@ int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,
} }
EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers); EXPORT_SYMBOL_GPL(call_switchdev_blocking_notifiers);
static int __switchdev_handle_fdb_add_to_device(struct net_device *dev,
const struct net_device *orig_dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info))
{
const struct switchdev_notifier_info *info = &fdb_info->info;
struct net_device *lower_dev;
struct list_head *iter;
int err = -EOPNOTSUPP;
if (check_cb(dev)) {
/* Handle FDB entries on foreign interfaces as FDB entries
* towards the software bridge.
*/
if (foreign_dev_check_cb && foreign_dev_check_cb(dev, orig_dev)) {
struct net_device *br = netdev_master_upper_dev_get_rcu(dev);
if (!br || !netif_is_bridge_master(br))
return 0;
/* No point in handling FDB entries on a foreign bridge */
if (foreign_dev_check_cb(dev, br))
return 0;
return __switchdev_handle_fdb_add_to_device(br, orig_dev,
fdb_info, check_cb,
foreign_dev_check_cb,
add_cb, lag_add_cb);
}
return add_cb(dev, orig_dev, info->ctx, fdb_info);
}
/* If we passed over the foreign check, it means that the LAG interface
* is offloaded.
*/
if (netif_is_lag_master(dev)) {
if (!lag_add_cb)
return -EOPNOTSUPP;
return lag_add_cb(dev, orig_dev, info->ctx, fdb_info);
}
/* Recurse through lower interfaces in case the FDB entry is pointing
* towards a bridge device.
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
/* Do not propagate FDB entries across bridges */
if (netif_is_bridge_master(lower_dev))
continue;
err = __switchdev_handle_fdb_add_to_device(lower_dev, orig_dev,
fdb_info, check_cb,
foreign_dev_check_cb,
add_cb, lag_add_cb);
if (err && err != -EOPNOTSUPP)
return err;
}
return err;
}
int switchdev_handle_fdb_add_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_add_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info))
{
int err;
err = __switchdev_handle_fdb_add_to_device(dev, dev, fdb_info,
check_cb,
foreign_dev_check_cb,
add_cb, lag_add_cb);
if (err == -EOPNOTSUPP)
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(switchdev_handle_fdb_add_to_device);
static int __switchdev_handle_fdb_del_to_device(struct net_device *dev,
const struct net_device *orig_dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info))
{
const struct switchdev_notifier_info *info = &fdb_info->info;
struct net_device *lower_dev;
struct list_head *iter;
int err = -EOPNOTSUPP;
if (check_cb(dev)) {
/* Handle FDB entries on foreign interfaces as FDB entries
* towards the software bridge.
*/
if (foreign_dev_check_cb && foreign_dev_check_cb(dev, orig_dev)) {
struct net_device *br = netdev_master_upper_dev_get_rcu(dev);
if (!br || !netif_is_bridge_master(br))
return 0;
/* No point in handling FDB entries on a foreign bridge */
if (foreign_dev_check_cb(dev, br))
return 0;
return __switchdev_handle_fdb_del_to_device(br, orig_dev,
fdb_info, check_cb,
foreign_dev_check_cb,
del_cb, lag_del_cb);
}
return del_cb(dev, orig_dev, info->ctx, fdb_info);
}
/* If we passed over the foreign check, it means that the LAG interface
* is offloaded.
*/
if (netif_is_lag_master(dev)) {
if (!lag_del_cb)
return -EOPNOTSUPP;
return lag_del_cb(dev, orig_dev, info->ctx, fdb_info);
}
/* Recurse through lower interfaces in case the FDB entry is pointing
* towards a bridge device.
*/
netdev_for_each_lower_dev(dev, lower_dev, iter) {
/* Do not propagate FDB entries across bridges */
if (netif_is_bridge_master(lower_dev))
continue;
err = switchdev_handle_fdb_del_to_device(lower_dev, fdb_info,
check_cb,
foreign_dev_check_cb,
del_cb, lag_del_cb);
if (err && err != -EOPNOTSUPP)
return err;
}
return err;
}
int switchdev_handle_fdb_del_to_device(struct net_device *dev,
const struct switchdev_notifier_fdb_info *fdb_info,
bool (*check_cb)(const struct net_device *dev),
bool (*foreign_dev_check_cb)(const struct net_device *dev,
const struct net_device *foreign_dev),
int (*del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info),
int (*lag_del_cb)(struct net_device *dev,
const struct net_device *orig_dev, const void *ctx,
const struct switchdev_notifier_fdb_info *fdb_info))
{
int err;
err = __switchdev_handle_fdb_del_to_device(dev, dev, fdb_info,
check_cb,
foreign_dev_check_cb,
del_cb, lag_del_cb);
if (err == -EOPNOTSUPP)
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(switchdev_handle_fdb_del_to_device);
static int __switchdev_handle_port_obj_add(struct net_device *dev, static int __switchdev_handle_port_obj_add(struct net_device *dev,
struct switchdev_notifier_port_obj_info *port_obj_info, struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev), bool (*check_cb)(const struct net_device *dev),
......
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