aboutsummaryrefslogtreecommitdiff
path: root/net/switchdev/switchdev.c
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2021-07-20 07:04:37 -0700
committerDavid S. Miller <davem@davemloft.net>2021-07-20 07:04:37 -0700
commit083cd5a42d0f676eb5ccb7c5b3a889d7453e367b (patch)
tree9f839966890a4400be58605618b5d98bd36a1be6 /net/switchdev/switchdev.c
parent7d901a1e878a1cf8dd3ba7b4c057ad5eb7a40af0 (diff)
parentb94dc99c0ddb74713da315853919393fb3e63b96 (diff)
downloadlinux-083cd5a42d0f676eb5ccb7c5b3a889d7453e367b.tar.gz
linux-083cd5a42d0f676eb5ccb7c5b3a889d7453e367b.tar.bz2
linux-083cd5a42d0f676eb5ccb7c5b3a889d7453e367b.zip
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: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/switchdev/switchdev.c')
-rw-r--r--net/switchdev/switchdev.c190
1 files changed, 190 insertions, 0 deletions
diff --git a/net/switchdev/switchdev.c b/net/switchdev/switchdev.c
index 070698dd19bc..82dd4e4e86f5 100644
--- a/net/switchdev/switchdev.c
+++ b/net/switchdev/switchdev.c
@@ -378,6 +378,196 @@ int call_switchdev_blocking_notifiers(unsigned long val, struct net_device *dev,
}
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,
struct switchdev_notifier_port_obj_info *port_obj_info,
bool (*check_cb)(const struct net_device *dev),