summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2025-11-05 13:20:42 +0200
committerNataliia Solomko <natalirs1985@gmail.com>2025-11-12 13:48:44 +0200
commitccf4972f8dd9ddf370fe6376480c1244a138c4bd (patch)
treec572124fe48db464fb48c912e070c0f322f12908
parent21d509d6c639f2a94c43c3af7f845ef0e544555f (diff)
downloadvyos-1x-ccf4972f8dd9ddf370fe6376480c1244a138c4bd.tar.gz
vyos-1x-ccf4972f8dd9ddf370fe6376480c1244a138c4bd.zip
T7731: Static ARP entries are missing after an interface status change
-rw-r--r--data/config-mode-dependencies/vyos-1x.json33
-rw-r--r--python/vyos/configdict.py10
-rwxr-xr-xsmoketest/scripts/cli/test_vpp.py24
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py6
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py24
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py8
-rwxr-xr-xsrc/conf_mode/interfaces_geneve.py9
-rwxr-xr-xsrc/conf_mode/interfaces_l2tpv3.py9
-rwxr-xr-xsrc/conf_mode/interfaces_macsec.py9
-rwxr-xr-xsrc/conf_mode/interfaces_pseudo-ethernet.py10
-rwxr-xr-xsrc/conf_mode/interfaces_virtual-ethernet.py9
-rwxr-xr-xsrc/conf_mode/interfaces_vxlan.py9
-rwxr-xr-xsrc/conf_mode/interfaces_wireless.py9
-rwxr-xr-xsrc/conf_mode/interfaces_wwan.py10
14 files changed, 169 insertions, 10 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index 56538883e..eb2cb2a04 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -8,11 +8,40 @@
"group_resync": ["system_conntrack", "nat", "policy_route", "load-balancing_wan"]
},
"interfaces_bonding": {
- "ethernet": ["interfaces_ethernet"]
+ "ethernet": ["interfaces_ethernet"],
+ "static_arp": ["protocols_static_arp"]
},
"interfaces_bridge": {
"vxlan": ["interfaces_vxlan"],
- "wlan": ["interfaces_wireless"]
+ "wlan": ["interfaces_wireless"],
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_ethernet": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_geneve": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_l2tpv3": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_macsec": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_pseudo_ethernet": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_virtual_ethernet": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_vxlan": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_wireless": {
+ "static_arp": ["protocols_static_arp"]
+ },
+ "interfaces_wwan": {
+ "static_arp": ["protocols_static_arp"]
},
"interfaces_wireguard": {
"vxlan": ["interfaces_vxlan"]
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index ea244247c..e1d05c384 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -613,6 +613,16 @@ def get_interface_dict(config, base, ifname='', recursive_defaults=True, with_pk
# Check vif, vif-s/vif-c VLAN interfaces for removal
dict = get_removed_vlans(config, base + [ifname], dict)
+
+ # Checks for the presence of static ARP entries on a given interface or VLAN
+ static_arp = config.get_config_dict(
+ ['protocols', 'static', 'arp', 'interface'],
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ )
+ if any(key == ifname or key.startswith(f'{ifname}.') for key in static_arp.keys()):
+ dict.update({'static_arp': {}})
+
return ifname, dict
def get_vlan_ids(interface):
diff --git a/smoketest/scripts/cli/test_vpp.py b/smoketest/scripts/cli/test_vpp.py
index 6efa04713..604fa25fc 100755
--- a/smoketest/scripts/cli/test_vpp.py
+++ b/smoketest/scripts/cli/test_vpp.py
@@ -1595,6 +1595,30 @@ class TestVPP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' hugepagesz={hp_size_1g} hugepages={hp_count_1g}', tmp)
self.assertIn(f' hugepagesz={hp_size_2m} hugepages={hp_count_2m}', tmp)
+ def test_21_static_arp(self):
+ host = '192.0.2.10'
+ mac = '00:01:02:03:04:0a'
+ path_static_arp = ['protocols', 'static', 'arp']
+
+ self.cli_set(['interfaces', 'ethernet', interface, 'address', '192.0.2.1/24'])
+ self.cli_set(
+ path_static_arp + ['interface', interface, 'address', host, 'mac', mac]
+ )
+ self.cli_commit()
+
+ # Change VPP configuration
+ self.cli_set(base_path + ['settings', 'unix', 'poll-sleep-usec', '50'])
+
+ # Ensure arp entry is not disappeared
+ _, neighbors = rc_cmd('sudo ip neighbor')
+ self.assertIn(f'{host} dev {interface} lladdr {mac}', neighbors)
+
+ # Check VPP IP neighbors
+ _, vpp_neighbors = rc_cmd('sudo vppctl show ip neighbors')
+ self.assertRegex(vpp_neighbors, rf'{host}\s+S\s+{mac}\s+{interface}')
+
+ self.cli_delete(path_static_arp)
+
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 4a2317f85..68c2885bc 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -175,6 +175,10 @@ def get_config(config=None):
tmp = has_vrf_configured(conf, interface)
if tmp: bond['member']['interface'][interface].update({'has_vrf' : ''})
+ # Protocols static arp dependency
+ if 'static_arp' in bond:
+ set_dependents('static_arp', conf)
+
return bond
@@ -279,7 +283,7 @@ def apply(bond):
else:
b.update(bond)
- if dict_search('member.interface_remove', bond):
+ if dict_search('member.interface_remove', bond) or 'static_arp' in bond:
try:
call_dependents()
except ConfigError:
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 8cb0c515a..c6792404b 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -124,6 +124,10 @@ def get_config(config=None):
if len(bridge['member']) == 0:
del bridge['member']
+ # Protocols static arp dependency
+ if 'static_arp' in bridge:
+ set_dependents('static_arp', conf)
+
return bridge
def verify(bridge):
@@ -213,12 +217,20 @@ def apply(bridge):
if 'interface' in bridge['member']:
tmp.extend(bridge['member']['interface'])
- for interface in tmp:
- if interface.startswith(tuple(['vxlan', 'wlan'])) and interface_exists(interface):
- try:
- call_dependents()
- except ConfigError:
- raise ConfigError(f'Error updating member interface {interface} configuration after changing bridge!')
+ # collect member interfaces that require dependent updates
+ interfaces_need_update = [
+ iface
+ for iface in tmp
+ if iface.startswith(('vxlan', 'wlan')) and interface_exists(iface)
+ ]
+
+ if interfaces_need_update or 'static_arp' in bridge:
+ try:
+ call_dependents()
+ except ConfigError:
+ raise ConfigError(
+ 'Error updating member interface configuration after changing bridge!'
+ )
return None
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index b112b244e..14bda1b3d 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -20,6 +20,8 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configdict import get_flowtable_interfaces
@@ -171,6 +173,10 @@ def get_config(config=None):
ethernet['flowtable_interfaces'] = get_flowtable_interfaces(conf)
+ # Protocols static arp dependency
+ if 'static_arp' in ethernet:
+ set_dependents('static_arp', conf)
+
return ethernet
def verify_speed_duplex(ethernet: dict, ethtool: Ethtool):
@@ -368,6 +374,8 @@ def apply(ethernet):
e.remove()
else:
e.update(ethernet)
+ if 'static_arp' in ethernet:
+ call_dependents()
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py
index 11b349002..2f3e3cb8c 100755
--- a/src/conf_mode/interfaces_geneve.py
+++ b/src/conf_mode/interfaces_geneve.py
@@ -17,6 +17,8 @@
from sys import exit
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
@@ -51,6 +53,10 @@ def get_config(config=None):
if is_node_changed(conf, base + [ifname, cli_option]):
geneve.update({'rebuild_required': {}})
+ # Protocols static arp dependency
+ if 'static_arp' in geneve:
+ set_dependents('static_arp', conf)
+
return geneve
def verify(geneve):
@@ -90,6 +96,9 @@ def apply(geneve):
g = GeneveIf(**geneve)
g.update(geneve)
+ if 'static_arp' in geneve:
+ call_dependents()
+
return None
diff --git a/src/conf_mode/interfaces_l2tpv3.py b/src/conf_mode/interfaces_l2tpv3.py
index 8eca1cedc..8478f3c40 100755
--- a/src/conf_mode/interfaces_l2tpv3.py
+++ b/src/conf_mode/interfaces_l2tpv3.py
@@ -17,6 +17,8 @@
from sys import exit
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_address
@@ -56,6 +58,10 @@ def get_config(config=None):
tmp = leaf_node_changed(conf, base + [ifname, 'session-id'])
l2tpv3.update({'session_id': tmp[0]})
+ # Protocols static arp dependency
+ if 'static_arp' in l2tpv3:
+ set_dependents('static_arp', conf)
+
return l2tpv3
def verify(l2tpv3):
@@ -100,6 +106,9 @@ def apply(l2tpv3):
l = L2TPv3If(**l2tpv3)
l.update(l2tpv3)
+ if 'static_arp' in l2tpv3:
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_macsec.py b/src/conf_mode/interfaces_macsec.py
index 683e12ec3..38823e7e6 100755
--- a/src/conf_mode/interfaces_macsec.py
+++ b/src/conf_mode/interfaces_macsec.py
@@ -19,6 +19,8 @@ import os
from sys import exit
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configdict import is_source_interface
@@ -78,6 +80,10 @@ def get_config(config=None):
tmp = is_source_interface(conf, macsec['source_interface'], ['macsec', 'pseudo-ethernet'])
if tmp and tmp != ifname: macsec.update({'is_source_interface' : tmp})
+ # Protocols static arp dependency
+ if 'static_arp' in macsec:
+ set_dependents('static_arp', conf)
+
return macsec
@@ -193,6 +199,9 @@ def apply(macsec):
if not is_systemd_service_running(systemd_service) or 'shutdown_required' in macsec:
call(f'systemctl reload-or-restart {systemd_service}')
+ if 'static_arp' in macsec:
+ call_dependents()
+
return None
diff --git a/src/conf_mode/interfaces_pseudo-ethernet.py b/src/conf_mode/interfaces_pseudo-ethernet.py
index 6edd3f248..7f9345abb 100755
--- a/src/conf_mode/interfaces_pseudo-ethernet.py
+++ b/src/conf_mode/interfaces_pseudo-ethernet.py
@@ -17,8 +17,9 @@
from sys import exit
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
-from vyos.configdict import is_node_changed
from vyos.configdict import is_source_interface
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
@@ -61,6 +62,10 @@ def get_config(config=None):
tmp = is_source_interface(conf, peth['source_interface'], ['macsec'])
if tmp and tmp != ifname: peth.update({'is_source_interface' : tmp})
+ # Protocols static arp dependency
+ if 'static_arp' in peth:
+ set_dependents('static_arp', conf)
+
return peth
def verify(peth):
@@ -95,6 +100,9 @@ def apply(peth):
p = MACVLANIf(**peth)
p.update(peth)
+ if 'static_arp' in peth:
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_virtual-ethernet.py b/src/conf_mode/interfaces_virtual-ethernet.py
index f85c3d1de..81a12c581 100755
--- a/src/conf_mode/interfaces_virtual-ethernet.py
+++ b/src/conf_mode/interfaces_virtual-ethernet.py
@@ -19,6 +19,8 @@ from sys import exit
from vyos import ConfigError
from vyos import airbag
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
@@ -47,6 +49,10 @@ def get_config(config=None):
veth['other_interfaces'] = conf.get_config_dict(base, key_mangling=('-', '_'),
get_first_key=True, no_tag_node_value_mangle=True)
+ # Protocols static arp dependency
+ if 'static_arp' in veth:
+ set_dependents('static_arp', conf)
+
return veth
@@ -101,6 +107,9 @@ def apply(veth):
p = VethIf(**veth)
p.update(veth)
+ if 'static_arp' in veth:
+ call_dependents()
+
return None
diff --git a/src/conf_mode/interfaces_vxlan.py b/src/conf_mode/interfaces_vxlan.py
index 81ebf8dda..5e23654e1 100755
--- a/src/conf_mode/interfaces_vxlan.py
+++ b/src/conf_mode/interfaces_vxlan.py
@@ -18,6 +18,8 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import leaf_node_changed
from vyos.configdict import is_node_changed
@@ -83,6 +85,10 @@ def get_config(config=None):
if len(vxlan['other_tunnels']) == 0:
del vxlan['other_tunnels']
+ # Protocols static arp dependency
+ if 'static_arp' in vxlan:
+ set_dependents('static_arp', conf)
+
return vxlan
def verify(vxlan):
@@ -249,6 +255,9 @@ def apply(vxlan):
v = VXLANIf(**vxlan)
v.update(vxlan)
+ if 'static_arp' in vxlan:
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_wireless.py b/src/conf_mode/interfaces_wireless.py
index b3b909046..a2e31484b 100755
--- a/src/conf_mode/interfaces_wireless.py
+++ b/src/conf_mode/interfaces_wireless.py
@@ -22,6 +22,8 @@ from netaddr import EUI, mac_unix_expanded
from time import sleep
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import dict_merge
from vyos.configverify import verify_address
@@ -133,6 +135,10 @@ def get_config(config=None):
wifi['hostapd_accept_station_conf'] = hostapd_accept_station_conf.format(**wifi)
wifi['hostapd_deny_station_conf'] = hostapd_deny_station_conf.format(**wifi)
+ # Protocols static arp dependency
+ if 'static_arp' in wifi:
+ set_dependents('static_arp', conf)
+
return wifi
def verify(wifi):
@@ -331,6 +337,9 @@ def apply(wifi):
elif wifi['type'] == 'station':
call(f'systemctl start wpa_supplicant@{interface}.service')
+ if 'static_arp' in wifi:
+ call_dependents()
+
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/interfaces_wwan.py b/src/conf_mode/interfaces_wwan.py
index ddddbe5e8..bde31ebe1 100755
--- a/src/conf_mode/interfaces_wwan.py
+++ b/src/conf_mode/interfaces_wwan.py
@@ -20,6 +20,8 @@ from sys import exit
from time import sleep
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import get_interface_dict
from vyos.configdict import is_node_changed
from vyos.configverify import verify_authentication
@@ -87,6 +89,10 @@ def get_config(config=None):
if len(wwan['other_interfaces']) == 0:
del wwan['other_interfaces']
+ # Protocols static arp dependency
+ if 'static_arp' in wwan:
+ set_dependents('static_arp', conf)
+
return wwan
def verify(wwan):
@@ -179,6 +185,10 @@ def apply(wwan):
call(command, stdout=DEVNULL)
w.update(wwan)
+
+ if 'static_arp' in wwan:
+ call_dependents()
+
return None
if __name__ == '__main__':