3131from vyos .configverify import verify_mtu_ipv6
3232from vyos .configverify import verify_mirror_redirect
3333from vyos .ifconfig import MACVLANIf
34+ from vyos .ifconfig import BridgeIf
3435from vyos .utils .network import interface_exists
36+ from vyos .utils .network import split_interface_vlans
3537from vyos import ConfigError
3638
3739from vyos import airbag
@@ -68,6 +70,35 @@ def get_config(config=None):
6870
6971 return peth
7072
73+ def _verify_anycast_gateway (peth : dict ):
74+ """Validate anycast-gateway requirements."""
75+
76+ if 'anycast_gateway' not in peth :
77+ return
78+
79+ ifname = peth ['ifname' ]
80+
81+ # Requirement 1: MAC address must be explicitly configured
82+ if 'mac' not in peth :
83+ raise ConfigError (
84+ f'Anycast-gateway requires an explicit MAC address to be set on interface { ifname } . '
85+ f'Use: set interfaces pseudo-ethernet { ifname } mac <mac>'
86+ )
87+
88+ # Requirement 2: source-interface must be a bridge or bridge sub-interface
89+ source_iface = peth .get ('source_interface' )
90+ if not source_iface :
91+ raise ConfigError (
92+ f'Anycast-gateway requires source-interface to be set on interface { ifname } '
93+ )
94+
95+ if not source_iface .startswith ('br' ):
96+ raise ConfigError (
97+ 'Anycast-gateway requires source-interface to be a bridge '
98+ 'or a bridge vlan interface (e.g. br0 or br0.100), but '
99+ f'"{ source_iface } " is neither of these two.'
100+ )
101+
71102def verify (peth ):
72103 if 'deleted' in peth :
73104 verify_bridge_delete (peth )
@@ -82,12 +113,75 @@ def verify(peth):
82113 # use common function to verify VLAN configuration
83114 verify_vlan_config (peth )
84115
116+ _verify_anycast_gateway (peth )
117+
85118 return None
86119
87120def generate (peth ):
88121 return None
89122
123+
124+ def _apply_anycast_gateway (peth : dict , old_mac : str | None , old_source : str | None ):
125+ """Apply anycast gateway FDB entry for bridge if configured."""
126+
127+ def _get_bridge_by_source (source_interface : str | None ):
128+ source_interface = source_interface or ''
129+ if source_interface .startswith ('br' ):
130+ bridge_ifname , * _ = split_interface_vlans (source_interface )
131+ if interface_exists (bridge_ifname ):
132+ return BridgeIf (bridge_ifname )
133+ return None
134+
135+ # Is anycast-gateway set in the new config?
136+ is_anycast = 'anycast_gateway' in peth
137+
138+ # Was anycast-gateway previously active?
139+ # (old entry exists if both old_mac and old_source set)
140+ was_anycast = old_mac is not None and old_source is not None
141+
142+ # Detect a MAC address change while anycast-gateway stays enabled
143+ new_mac = peth .get ('mac' )
144+ mac_changed = is_anycast and old_mac and old_mac != new_mac
145+
146+ # Detect a source interface change while anycast-gateway stays enabled
147+ new_source = peth .get ('source_interface' )
148+ source_changed = is_anycast and old_source and old_source != new_source
149+
150+ # Clean up old FDB entry in the next situations:
151+ # 1. anycast-gateway was removed
152+ # 2. the MAC address changed (old entry is now stale)
153+ # 3. the source interface changed (old entry is now stale)
154+ # 4. the interface is being deleted
155+ if not is_anycast or mac_changed or source_changed or 'deleted' in peth :
156+ if old_source and old_mac :
157+ bridge = _get_bridge_by_source (old_source )
158+ if bridge :
159+ try :
160+ bridge .del_local_fdb_entry (old_mac )
161+ except OSError :
162+ pass # Bridge may already be gone, that is fine
163+
164+ if 'deleted' not in peth :
165+ # Add only on transition or key change
166+ should_add = (is_anycast and not was_anycast ) or mac_changed or source_changed
167+ if should_add and new_source and new_mac :
168+ bridge = _get_bridge_by_source (new_source )
169+ if bridge :
170+ bridge .add_local_fdb_entry (new_mac )
171+
172+
90173def apply (peth ):
174+ # Obtain current MAC address and source interface if pseudo-ethernet exists.
175+ # It is neccessary to apply anycast gateway.
176+ current_mac = current_source = None
177+ if 'deleted' not in peth :
178+ peth_ifname = peth ['ifname' ]
179+ if interface_exists (peth_ifname ):
180+ piface = MACVLANIf (peth_ifname , create = False )
181+ if piface :
182+ current_mac = piface .get_mac ()
183+ current_source = piface .get_source_interface ()
184+
91185 # Check if the MACVLAN interface already exists
92186 if 'rebuild_required' in peth or 'deleted' in peth :
93187 if interface_exists (peth ['ifname' ]):
@@ -100,6 +194,8 @@ def apply(peth):
100194 p = MACVLANIf (** peth )
101195 p .update (peth )
102196
197+ _apply_anycast_gateway (peth , current_mac , current_source )
198+
103199 if 'static_arp' in peth :
104200 call_dependents ()
105201
0 commit comments