diff options
| author | Christian Breunig <christian@breunig.cc> | 2026-10-07 20:44:34 +0200 |
|---|---|---|
| committer | Christian Breunig <christian@breunig.cc> | 2026-10-08 21:36:33 +0200 |
| commit | e3643b21a507dc744b9b64d919a555ff597cc71c (patch) | |
| tree | bbb1225dd3ec98f436e72d87fd250141faed948d /python | |
| parent | 1ac0219e62c7b0efee9e81e7266558dffc7b14e6 (diff) | |
| download | vyos-1x-e3643b21a507dc744b9b64d919a555ff597cc71c.tar.gz vyos-1x-e3643b21a507dc744b9b64d919a555ff597cc71c.zip | |
pseudo-ethernet: T9253: fix anycast-gateway with shared MAC on bridge VLANs
Several anycast-gateway pseudo-ethernets with the same MAC on different
VLANs of one bridge (the usual EVPN layout) failed to commit with
"bridge fdb add ... File exists". The local FDB entry was always installed
without a VLAN, so every interface competed for the same entry. A failing
commit at boot left only one interface configured and dropped the rest of
the config.
The local FDB entry is now bound to the VLAN of the bridge sub-interface
when the bridge is VLAN aware, and it is installed with replace so a
re-commit over an existing entry no longer fails. Two anycast-gateways
with the same MAC on the same source-interface are rejected.
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/ifconfig/bridge.py | 28 | ||||
| -rw-r--r-- | python/vyos/ifconfig/macvlan.py | 29 |
2 files changed, 35 insertions, 22 deletions
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index f04d52546..448d743fe 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -113,8 +113,8 @@ class BridgeIf(Interface): 'del_port': { 'shellcmd': 'ip link set dev {value} nomaster', }, - 'add_local_fdb_entry': { - 'shellcmd': 'bridge fdb add {value} dev {ifname} self local', + 'replace_local_fdb_entry': { + 'shellcmd': 'bridge fdb replace {value} dev {ifname} self local', }, 'del_local_fdb_entry': { 'shellcmd': 'bridge fdb del {value} dev {ifname} self local', @@ -292,25 +292,33 @@ class BridgeIf(Interface): """ return self.get_interface('fdb_entries') - def add_local_fdb_entry(self, mac_address: str): + def replace_local_fdb_entry(self, mac_address: str, vlan=None): """ - Add a local FDB entry for the given MAC address on the bridge interface. + Add or replace a local FDB entry for the given MAC address on the bridge + interface, optionally bound to a VLAN. Example: >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').add_local_fdb_entry('cc:38:2e:bf:7b:0d') + >>> BridgeIf('br0').replace_local_fdb_entry('cc:38:2e:bf:7b:0d', 100) """ - self.set_interface('add_local_fdb_entry', mac_address.lower()) + value = [mac_address.lower()] + if vlan: + value += ['vlan', str(vlan)] + self.set_interface('replace_local_fdb_entry', ' '.join(value)) - def del_local_fdb_entry(self, mac_address: str): + def del_local_fdb_entry(self, mac_address: str, vlan=None): """ - Remove a local FDB entry for the given MAC address on the bridge interface. + Remove a local FDB entry for the given MAC address on the bridge interface, + optionally bound to a VLAN. Example: >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').del_local_fdb_entry('cc:38:2e:bf:7b:0d') + >>> BridgeIf('br0').del_local_fdb_entry('cc:38:2e:bf:7b:0d', 100) """ - self.set_interface('del_local_fdb_entry', mac_address.lower()) + value = [mac_address.lower()] + if vlan: + value += ['vlan', str(vlan)] + self.set_interface('del_local_fdb_entry', ' '.join(value)) def update(self, config): """ General helper function which works on a dictionary retrieved by diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index 0626b3d21..acf17f0ce 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -45,40 +45,45 @@ class MACVLANIf(Interface): # interface is always A/D down. It needs to be enabled explicitly self.set_admin_state('down') - def _get_bridge_by_source(self, source_interface: str) -> BridgeIf | None: + def _get_bridge_by_source(self, source_interface: str) -> tuple: """ - Resolve a source interface name to its root BridgeIf object. + Resolve a source interface name to its root BridgeIf object and the + bridge VLAN the anycast MAC belongs to. Handles plain bridge names (e.g. 'br0') and bridge sub-interfaces - with one or two VLAN suffixes (e.g. 'br0.100', 'br0.100.200'). + with one or two VLAN suffixes (e.g. 'br0.100', 'br0.100.200'). The + first suffix is the bridge VLAN. It is only returned for VLAN-aware + bridges, the kernel rejects VLAN FDB entries on any other bridge. + + Returns (bridge, vlan), bridge is None if it does not exist. """ - bridge = None + bridge = vlan = None if source_interface.startswith('br'): - # We only need the root bridge name, so unpack with *_ to - # discard the VLAN parts. - bridge_ifname, *_ = split_interface_vlans(source_interface) + bridge_ifname, vlan, _ = split_interface_vlans(source_interface) if interface_exists(bridge_ifname): bridge = BridgeIf(bridge_ifname) + if not int(bridge.get_vlan_filter()): + vlan = None - return bridge + return bridge, vlan def _create_anycast_gateway(self, source_interface, mac): """Install a local FDB entry on the parent bridge for the anycast MAC""" if source_interface and mac: - bridge = self._get_bridge_by_source(source_interface) + bridge, vlan = self._get_bridge_by_source(source_interface) if bridge: - bridge.add_local_fdb_entry(mac) + bridge.replace_local_fdb_entry(mac, vlan) def _delete_anycast_gateway(self, source_interface, mac): """Remove the local FDB entry from the parent bridge for the anycast MAC""" if source_interface and mac: - bridge = self._get_bridge_by_source(source_interface) + bridge, vlan = self._get_bridge_by_source(source_interface) if bridge: try: - bridge.del_local_fdb_entry(mac) + bridge.del_local_fdb_entry(mac, vlan) except OSError: pass # Bridge may already be gone, that is fine |
