Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dsa: update the unicast MAC address when changing conduit

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>

authored by

Marek Behún and committed by
David S. Miller
eef8e906 77f75412

+44 -8
+40
net/dsa/port.c
··· 1467 1467 */ 1468 1468 dsa_user_unsync_ha(dev); 1469 1469 1470 + /* If live-changing, we also need to uninstall the user device address 1471 + * from the port FDB and the conduit interface. 1472 + */ 1473 + if (dev->flags & IFF_UP) 1474 + dsa_user_host_uc_uninstall(dev); 1475 + 1470 1476 err = dsa_port_assign_conduit(dp, conduit, extack, true); 1471 1477 if (err) 1472 1478 goto rewind_old_addrs; 1479 + 1480 + /* If the port doesn't have its own MAC address and relies on the DSA 1481 + * conduit's one, inherit it again from the new DSA conduit. 1482 + */ 1483 + if (is_zero_ether_addr(dp->mac)) 1484 + eth_hw_addr_inherit(dev, conduit); 1485 + 1486 + /* If live-changing, we need to install the user device address to the 1487 + * port FDB and the conduit interface. 1488 + */ 1489 + if (dev->flags & IFF_UP) { 1490 + err = dsa_user_host_uc_install(dev, dev->dev_addr); 1491 + if (err) { 1492 + NL_SET_ERR_MSG_MOD(extack, 1493 + "Failed to install host UC address"); 1494 + goto rewind_addr_inherit; 1495 + } 1496 + } 1473 1497 1474 1498 dsa_user_sync_ha(dev); 1475 1499 ··· 1524 1500 rewind_new_addrs: 1525 1501 dsa_user_unsync_ha(dev); 1526 1502 1503 + if (dev->flags & IFF_UP) 1504 + dsa_user_host_uc_uninstall(dev); 1505 + 1506 + rewind_addr_inherit: 1507 + if (is_zero_ether_addr(dp->mac)) 1508 + eth_hw_addr_inherit(dev, old_conduit); 1509 + 1527 1510 dsa_port_assign_conduit(dp, old_conduit, NULL, false); 1528 1511 1529 1512 /* Restore the objects on the old CPU port */ 1530 1513 rewind_old_addrs: 1514 + if (dev->flags & IFF_UP) { 1515 + tmp = dsa_user_host_uc_install(dev, dev->dev_addr); 1516 + if (tmp) { 1517 + dev_err(ds->dev, 1518 + "port %d failed to restore host UC address: %pe\n", 1519 + dp->index, ERR_PTR(tmp)); 1520 + } 1521 + } 1522 + 1531 1523 dsa_user_sync_ha(dev); 1532 1524 1533 1525 if (vlan_filtering) {
+2 -8
net/dsa/user.c
··· 355 355 return READ_ONCE(dsa_user_to_conduit(dev)->ifindex); 356 356 } 357 357 358 - static int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr) 358 + int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr) 359 359 { 360 360 struct net_device *conduit = dsa_user_to_conduit(dev); 361 361 struct dsa_port *dp = dsa_user_to_port(dev); ··· 383 383 return err; 384 384 } 385 385 386 - static void dsa_user_host_uc_uninstall(struct net_device *dev) 386 + void dsa_user_host_uc_uninstall(struct net_device *dev) 387 387 { 388 388 struct net_device *conduit = dsa_user_to_conduit(dev); 389 389 struct dsa_port *dp = dsa_user_to_port(dev); ··· 2881 2881 "nonfatal error updating MTU with new conduit: %pe\n", 2882 2882 ERR_PTR(err)); 2883 2883 } 2884 - 2885 - /* If the port doesn't have its own MAC address and relies on the DSA 2886 - * conduit's one, inherit it again from the new DSA conduit. 2887 - */ 2888 - if (is_zero_ether_addr(dp->mac)) 2889 - eth_hw_addr_inherit(dev, conduit); 2890 2884 2891 2885 return 0; 2892 2886
+2
net/dsa/user.h
··· 42 42 int dsa_user_resume(struct net_device *user_dev); 43 43 int dsa_user_register_notifier(void); 44 44 void dsa_user_unregister_notifier(void); 45 + int dsa_user_host_uc_install(struct net_device *dev, const u8 *addr); 46 + void dsa_user_host_uc_uninstall(struct net_device *dev); 45 47 void dsa_user_sync_ha(struct net_device *dev); 46 48 void dsa_user_unsync_ha(struct net_device *dev); 47 49 void dsa_user_setup_tagger(struct net_device *user);