summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNataliia Solomko <natalirs1985@gmail.com>2026-06-22 18:58:14 +0300
committerNataliia Solomko <natalirs1985@gmail.com>2026-06-24 11:37:16 +0300
commit68cb83099e4f71d60019eacca88c8892a4247d01 (patch)
tree291e77ae8f8f28d165e4d9c49fb1db83b81985b3
parent02f8c17a56518157e48048fb39b69f373ee9c829 (diff)
downloadvyos-1x-68cb83099e4f71d60019eacca88c8892a4247d01.tar.gz
vyos-1x-68cb83099e4f71d60019eacca88c8892a4247d01.zip
T9011: enable proxy_ndp sysctl for static IPv6 neighbor proxy
-rw-r--r--smoketest/scripts/cli/test_protocols_static_neighbor-proxy.py90
-rwxr-xr-xsrc/conf_mode/protocols_static_neighbor-proxy.py29
2 files changed, 117 insertions, 2 deletions
diff --git a/smoketest/scripts/cli/test_protocols_static_neighbor-proxy.py b/smoketest/scripts/cli/test_protocols_static_neighbor-proxy.py
new file mode 100644
index 000000000..beed3156c
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_static_neighbor-proxy.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+#
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.utils.system import sysctl_read
+from vyos.utils.process import cmd
+
+base_path = ['protocols', 'static', 'neighbor-proxy']
+interface = 'eth0'
+
+
+class TestProtocolsStaticNeighborProxy(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestProtocolsStaticNeighborProxy, cls).setUpClass()
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+ super().tearDown()
+
+ def test_arp_proxy(self):
+ address = '192.0.2.1/24'
+ neighbor = '192.0.2.10'
+
+ self.cli_set(['interfaces', 'ethernet', interface, 'address', address])
+ self.cli_set(base_path + ['arp', neighbor, 'interface', interface])
+ self.cli_commit()
+
+ proxy_entries = cmd('ip -4 neigh show proxy')
+ self.assertIn(f'{neighbor} dev {interface} proxy', proxy_entries)
+
+ self.cli_delete(['interfaces', 'ethernet', interface, 'address', address])
+
+ def test_nd_proxy(self):
+ address = 'fd00::1/64'
+ neighbor = 'fd00::99'
+ interface2 = 'eth1'
+
+ self.cli_set(['interfaces', 'ethernet', interface, 'address', address])
+ # Add ND proxy on two interfaces
+ self.cli_set(base_path + ['nd', neighbor, 'interface', interface])
+ self.cli_set(base_path + ['nd', neighbor, 'interface', interface2])
+ self.cli_commit()
+
+ # Verify proxy entries are installed
+ proxy_entries = cmd('ip -6 neigh show proxy')
+ for iface in [interface, interface2]:
+ self.assertIn(f'{neighbor} dev {iface} proxy', proxy_entries)
+ # Verify proxy_ndp sysctl is enabled on both interfaces
+ self.assertEqual(
+ sysctl_read(['net', 'ipv6', 'conf', iface, 'proxy_ndp']), '1'
+ )
+
+ # Remove one interface
+ self.cli_delete(base_path + ['nd', neighbor, 'interface', interface2])
+ self.cli_commit()
+
+ # First interface still enabled
+ self.assertEqual(
+ sysctl_read(['net', 'ipv6', 'conf', interface, 'proxy_ndp']), '1'
+ )
+ # Removed interface disabled
+ self.assertEqual(
+ sysctl_read(['net', 'ipv6', 'conf', interface2, 'proxy_ndp']), '0'
+ )
+
+ self.cli_delete(['interfaces', 'ethernet', interface, 'address', address])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/protocols_static_neighbor-proxy.py b/src/conf_mode/protocols_static_neighbor-proxy.py
index bda737e75..0c8fd8483 100755
--- a/src/conf_mode/protocols_static_neighbor-proxy.py
+++ b/src/conf_mode/protocols_static_neighbor-proxy.py
@@ -17,11 +17,16 @@
from sys import exit
from vyos.config import Config
+from vyos.configdict import leaf_node_changed
+from vyos.configdict import node_changed
from vyos.utils.process import call
+from vyos.utils.system import sysctl_write
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
+
def get_config(config=None):
if config:
conf = config
@@ -31,8 +36,19 @@ def get_config(config=None):
base = ['protocols', 'static', 'neighbor-proxy']
config = conf.get_config_dict(base, get_first_key=True)
+ if not config:
+ config = {'deleted': True}
+
+ removed_nd_interfaces = set()
+ for neighbor in node_changed(conf, base + ['nd'], recursive=True):
+ tmp = leaf_node_changed(conf, base + ['nd', neighbor, 'interface'])
+ if tmp:
+ removed_nd_interfaces.update(tmp)
+ config['removed_nd_interfaces'] = removed_nd_interfaces
+
return config
+
def verify(config):
if 'arp' in config:
for neighbor, neighbor_conf in config['arp'].items():
@@ -45,14 +61,21 @@ def verify(config):
for neighbor, neighbor_conf in config['nd'].items():
if 'interface' not in neighbor_conf:
raise ConfigError(
- f"ARP neighbor-proxy for '{neighbor}' requires an interface to be set!"
+ f"NDP neighbor-proxy for '{neighbor}' requires an interface to be set!"
)
+
def generate(config):
pass
+
def apply(config):
- if not config:
+ # Disable proxy_ndp on interfaces removed from config
+ removed_nd_interfaces = config.pop('removed_nd_interfaces', set())
+ for iface in removed_nd_interfaces:
+ sysctl_write(['net', 'ipv6', 'conf', iface, 'proxy_ndp'], 0)
+
+ if 'deleted' in config:
# Cleanup proxy
call('ip neighbor flush proxy')
call('ip -6 neighbor flush proxy')
@@ -73,6 +96,8 @@ def apply(config):
for neighbor, neighbor_conf in config['nd'].items():
for interface in neighbor_conf['interface']:
call(f'ip -6 neighbor add proxy {neighbor} dev {interface}')
+ sysctl_write(['net', 'ipv6', 'conf', interface, 'proxy_ndp'], 1)
+
if __name__ == '__main__':
try: