summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2026-10-07 20:44:34 +0200
committerChristian Breunig <christian@breunig.cc>2026-10-08 21:36:33 +0200
commite3643b21a507dc744b9b64d919a555ff597cc71c (patch)
treebbb1225dd3ec98f436e72d87fd250141faed948d /python
parent1ac0219e62c7b0efee9e81e7266558dffc7b14e6 (diff)
downloadvyos-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.py28
-rw-r--r--python/vyos/ifconfig/macvlan.py29
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