]> www.infradead.org Git - users/jedix/linux-maple.git/commitdiff
net: dsa: update the unicast MAC address when changing conduit
authorMarek BehĂșn <kabel@kernel.org>
Wed, 5 Jun 2024 13:33:29 +0000 (15:33 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 10 Jun 2024 12:48:06 +0000 (13:48 +0100)
When changing DSA user interface conduit while the user interface is up,
DSA exhibits different behavior in comparison to when the interface is
down. This different behavior concerns the primary unicast MAC address
stored in the port standalone FDB and in the conduit device UC database.

If we put a switch port down while changing the conduit with
  ip link set sw0p0 down
  ip link set sw0p0 type dsa conduit conduit1
  ip link set sw0p0 up
we delete the address in dsa_user_close() and install the (possibly
different) address in dsa_user_open().

But when changing the conduit on the fly, the old address is not
deleted and the new one is not installed.

Since we explicitly want to support live-changing the conduit, uninstall
the old address before calling dsa_port_assign_conduit() and install the
(possibly different) new address after the call.

Because conduit change might also trigger address change (the user
interface is supposed to inherit the conduit interface MAC address if no
address is defined in hardware (dp->mac is a zero address)), move the
eth_hw_addr_inherit() call from dsa_user_change_conduit() to
dsa_port_change_conduit(), just before installing the new address.

Although this is in theory a flaw in DSA core, it needs not be
backported, since there is currently no DSA driver that can be affected
by this. The only DSA driver that supports changing conduit is felix,
and, as explained by Vladimir Oltean [1]:

  There are 2 reasons why with felix the bug does not manifest itself.

  First is because both the 'ocelot' and the alternate 'ocelot-8021q'
  tagging protocols have the 'promisc_on_conduit = true' flag. So the
  unicast address doesn't have to be in the conduit's RX filter -
  neither the old or the new conduit.

  Second, dsa_user_host_uc_install() theoretically leaves behind host
  FDB entries installed towards the wrong (old) CPU port. But in
  felix_fdb_add(), we treat any FDB entry requested towards any CPU port
  as if it was a multicast FDB entry programmed towards _all_ CPU ports.
  For that reason, it is installed towards the port mask of the PGID_CPU
  port group ID:

if (dsa_port_is_cpu(dp))
port = PGID_CPU;

Therefore no Fixes tag for this change.

[1] https://lore.kernel.org/netdev/20240507201827.47suw4fwcjrbungy@skbuf/
Signed-off-by: Marek BehĂșn <kabel@kernel.org>
Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
Tested-by: Vladimir Oltean <olteanv@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/dsa/port.c
net/dsa/user.c
net/dsa/user.h

index e23db9507546b14303ba45d473f51221daeb829a..25258b33e59e015db7c4be9a7d403e318b21af71 100644 (file)
@@ -1467,10 +1467,34 @@ int dsa_port_change_conduit(struct dsa_port *dp, struct net_device *conduit,
         */
        dsa_user_unsync_ha(dev);
 
+       /* If live-changing, we also need to uninstall the user device address
+        * from the port FDB and the conduit interface.
+        */
+       if (dev->flags & IFF_UP)
+               dsa_user_host_uc_uninstall(dev);
+
        err = dsa_port_assign_conduit(dp, conduit, extack, true);
        if (err)
                goto rewind_old_addrs;
 
+       /* If the port doesn't have its own MAC address and relies on the DSA
+        * conduit's one, inherit it again from the new DSA conduit.
+        */
+       if (is_zero_ether_addr(dp->mac))
+               eth_hw_addr_inherit(dev, conduit);
+
+       /* If live-changing, we need to install the user device address to the
+        * port FDB and the conduit interface.
+        */
+       if (dev->flags & IFF_UP) {
+               err = dsa_user_host_uc_install(dev, dev->dev_addr);
+               if (err) {
+                       NL_SET_ERR_MSG_MOD(extack,
+                                          "Failed to install host UC address");
+                       goto rewind_addr_inherit;
+               }
+       }
+
        dsa_user_sync_ha(dev);
 
        if (vlan_filtering) {
@@ -1500,10 +1524,26 @@ rewind_new_vlan:
 rewind_new_addrs:
        dsa_user_unsync_ha(dev);
 
+       if (dev->flags & IFF_UP)
+               dsa_user_host_uc_uninstall(dev);
+
+rewind_addr_inherit:
+       if (is_zero_ether_addr(dp->mac))
+               eth_hw_addr_inherit(dev, old_conduit);
+
        dsa_port_assign_conduit(dp, old_conduit, NULL, false);
 
 /* Restore the objects on the old CPU port */
 rewind_old_addrs:
+       if (dev->flags & IFF_UP) {
+               tmp = dsa_user_host_uc_install(dev, dev->dev_addr);
+               if (tmp) {
+                       dev_err(ds->dev,
+                               "port %d failed to restore host UC address: %pe\n",
+                               dp->index, ERR_PTR(tmp));
+               }
+       }
+
        dsa_user_sync_ha(dev);
 
        if (vlan_filtering) {
index efbb34c711cb92b23061a7298bd17e61ca6c0aa4..e8f56a40b6141500dd0e3b26716579c6618e7e66 100644 (file)
@@ -355,7 +355,7 @@ static int dsa_user_get_iflink(const struct net_device *dev)
        return READ_ONCE(dsa_user_to_conduit(dev)->ifindex);
 }
 
-static int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr)
+int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr)
 {
        struct net_device *conduit = dsa_user_to_conduit(dev);
        struct dsa_port *dp = dsa_user_to_port(dev);
@@ -383,7 +383,7 @@ out:
        return err;
 }
 
-static void dsa_user_host_uc_uninstall(struct net_device *dev)
+void dsa_user_host_uc_uninstall(struct net_device *dev)
 {
        struct net_device *conduit = dsa_user_to_conduit(dev);
        struct dsa_port *dp = dsa_user_to_port(dev);
@@ -2882,12 +2882,6 @@ int dsa_user_change_conduit(struct net_device *dev, struct net_device *conduit,
                            ERR_PTR(err));
        }
 
-       /* If the port doesn't have its own MAC address and relies on the DSA
-        * conduit's one, inherit it again from the new DSA conduit.
-        */
-       if (is_zero_ether_addr(dp->mac))
-               eth_hw_addr_inherit(dev, conduit);
-
        return 0;
 
 out_revert_conduit_link:
index 996069130beae81afdf5184e87677c302786b6f7..016884bead3cd7c147b91d4c3e902e926a917b04 100644 (file)
@@ -42,6 +42,8 @@ int dsa_user_suspend(struct net_device *user_dev);
 int dsa_user_resume(struct net_device *user_dev);
 int dsa_user_register_notifier(void);
 void dsa_user_unregister_notifier(void);
+int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr);
+void dsa_user_host_uc_uninstall(struct net_device *dev);
 void dsa_user_sync_ha(struct net_device *dev);
 void dsa_user_unsync_ha(struct net_device *dev);
 void dsa_user_setup_tagger(struct net_device *user);