diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2 index 97180d164..a83bd03ac 100644 --- a/data/templates/router-advert/radvd.conf.j2 +++ b/data/templates/router-advert/radvd.conf.j2 @@ -1,85 +1,85 @@ ### Autogenerated by service_router-advert.py ### {% if interface is vyos_defined %} {% for iface, iface_config in interface.items() %} interface {{ iface }} { IgnoreIfMissing on; {% if iface_config.default_preference is vyos_defined %} AdvDefaultPreference {{ iface_config.default_preference }}; {% endif %} {% if iface_config.managed_flag is vyos_defined %} AdvManagedFlag {{ 'on' if iface_config.managed_flag is vyos_defined else 'off' }}; {% endif %} {% if iface_config.interval.max is vyos_defined %} MaxRtrAdvInterval {{ iface_config.interval.max }}; {% endif %} {% if iface_config.interval.min is vyos_defined %} MinRtrAdvInterval {{ iface_config.interval.min }}; {% endif %} {% if iface_config.reachable_time is vyos_defined %} AdvReachableTime {{ iface_config.reachable_time }}; {% endif %} - AdvIntervalOpt {{ 'off' if iface_config.no_send_advert is vyos_defined else 'on' }}; + AdvIntervalOpt {{ 'off' if iface_config.no_send_interval is vyos_defined else 'on' }}; AdvSendAdvert {{ 'off' if iface_config.no_send_advert is vyos_defined else 'on' }}; {% if iface_config.default_lifetime is vyos_defined %} AdvDefaultLifetime {{ iface_config.default_lifetime }}; {% endif %} {% if iface_config.link_mtu is vyos_defined %} AdvLinkMTU {{ iface_config.link_mtu }}; {% endif %} AdvOtherConfigFlag {{ 'on' if iface_config.other_config_flag is vyos_defined else 'off' }}; AdvRetransTimer {{ iface_config.retrans_timer }}; AdvCurHopLimit {{ iface_config.hop_limit }}; {% if iface_config.route is vyos_defined %} {% for route, route_options in iface_config.route.items() %} route {{ route }} { {% if route_options.valid_lifetime is vyos_defined %} AdvRouteLifetime {{ route_options.valid_lifetime }}; {% endif %} {% if route_options.route_preference is vyos_defined %} AdvRoutePreference {{ route_options.route_preference }}; {% endif %} RemoveRoute {{ 'off' if route_options.no_remove_route is vyos_defined else 'on' }}; }; {% endfor %} {% endif %} {% if iface_config.source_address is vyos_defined %} AdvRASrcAddress { {% for source_address in iface_config.source_address %} {{ source_address }}; {% endfor %} }; {% endif %} {% if iface_config.nat64prefix is vyos_defined %} {% for nat64prefix, nat64prefix_options in iface_config.nat64prefix.items() %} nat64prefix {{ nat64prefix }} { AdvValidLifetime {{ nat64prefix_options.valid_lifetime }}; }; {% endfor %} {% endif %} {% if iface_config.prefix is vyos_defined %} {% for prefix, prefix_options in iface_config.prefix.items() %} prefix {{ prefix }} { AdvAutonomous {{ 'off' if prefix_options.no_autonomous_flag is vyos_defined else 'on' }}; AdvValidLifetime {{ prefix_options.valid_lifetime }}; AdvOnLink {{ 'off' if prefix_options.no_on_link_flag is vyos_defined else 'on' }}; AdvPreferredLifetime {{ prefix_options.preferred_lifetime }}; DeprecatePrefix {{ 'on' if prefix_options.deprecate_prefix is vyos_defined else 'off' }}; DecrementLifetimes {{ 'on' if prefix_options.decrement_lifetime is vyos_defined else 'off' }}; }; {% endfor %} {% endif %} {% if iface_config.name_server is vyos_defined %} RDNSS {{ iface_config.name_server | join(" ") }} { {% if iface_config.name_server_lifetime is vyos_defined %} AdvRDNSSLifetime {{ iface_config.name_server_lifetime }}; {% endif %} }; {% endif %} {% if iface_config.dnssl is vyos_defined %} DNSSL {{ iface_config.dnssl | join(" ") }} { }; {% endif %} }; {% endfor %} {% endif %} diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in index 166a4a0cf..3fd33540a 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -1,399 +1,405 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="service"> <children> <node name="router-advert" owner="${vyos_conf_scripts_dir}/service_router-advert.py"> <properties> <help>IPv6 Router Advertisements (RAs) service</help> <priority>900</priority> </properties> <children> <tagNode name="interface"> <properties> <help>Interface to send RA on</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> </properties> <children> <leafNode name="hop-limit"> <properties> <help>Set Hop Count field of the IP header for outgoing packets</help> <valueHelp> <format>u32:0</format> <description>Unspecified (by this router)</description> </valueHelp> <valueHelp> <format>u32:1-255</format> <description>Value should represent current diameter of the Internet</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-255"/> </constraint> <constraintErrorMessage>Hop count must be between 0 and 255</constraintErrorMessage> </properties> <defaultValue>64</defaultValue> </leafNode> <leafNode name="default-lifetime"> <properties> <help>Lifetime associated with the default router in units of seconds</help> <valueHelp> <format>u32:4-9000</format> <description>Router Lifetime in seconds</description> </valueHelp> <valueHelp> <format>0</format> <description>Not a default router</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 4-9000"/> </constraint> <constraintErrorMessage>Default router livetime bust be 0 or between 4 and 9000</constraintErrorMessage> </properties> </leafNode> <leafNode name="default-preference"> <properties> <help>Preference associated with the default router,</help> <completionHelp> <list>low medium high</list> </completionHelp> <valueHelp> <format>low</format> <description>Default router has low preference</description> </valueHelp> <valueHelp> <format>medium</format> <description>Default router has medium preference</description> </valueHelp> <valueHelp> <format>high</format> <description>Default router has high preference</description> </valueHelp> <constraint> <regex>(low|medium|high)</regex> </constraint> <constraintErrorMessage>Default preference must be low, medium or high</constraintErrorMessage> </properties> <defaultValue>medium</defaultValue> </leafNode> <leafNode name="dnssl"> <properties> <help>DNS search list</help> <multi/> </properties> </leafNode> <leafNode name="link-mtu"> <properties> <help>Link MTU value placed in RAs, exluded in RAs if unset</help> <valueHelp> <format>u32:1280-9000</format> <description>Link MTU value in RAs</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1280-9000"/> </constraint> <constraintErrorMessage>Link MTU must be between 1280 and 9000</constraintErrorMessage> </properties> </leafNode> <leafNode name="managed-flag"> <properties> <help>Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC</help> <valueless/> </properties> </leafNode> <node name="interval"> <properties> <help>Set interval between unsolicited multicast RAs</help> </properties> <children> <leafNode name="max"> <properties> <help>Maximum interval between unsolicited multicast RAs</help> <valueHelp> <format>u32:4-1800</format> <description>Maximum interval in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 4-1800"/> </constraint> <constraintErrorMessage>Maximum interval must be between 4 and 1800 seconds</constraintErrorMessage> </properties> <defaultValue>600</defaultValue> </leafNode> <leafNode name="min"> <properties> <help>Minimum interval between unsolicited multicast RAs</help> <valueHelp> <format>u32:3-1350</format> <description>Minimum interval in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 3-1350"/> </constraint> <constraintErrorMessage>Minimum interval must be between 3 and 1350 seconds</constraintErrorMessage> </properties> </leafNode> </children> </node> #include <include/name-server-ipv6.xml.i> <leafNode name="name-server-lifetime"> <properties> <help>Maximum duration how long the RDNSS entries are used</help> <valueHelp> <format>u32:0</format> <description>Name-servers should no longer be used</description> </valueHelp> <valueHelp> <format>u32:1-7200</format> <description>Maximum interval in seconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 1-7200"/> </constraint> <constraintErrorMessage>Maximum interval must be between 1 and 7200 seconds</constraintErrorMessage> </properties> </leafNode> <leafNode name="other-config-flag"> <properties> <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help> <valueless/> </properties> </leafNode> <tagNode name="route"> <properties> <help>IPv6 route to be advertised in Router Advertisements (RAs)</help> <valueHelp> <format>ipv6net</format> <description>IPv6 route to be advertized</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> <children> <leafNode name="valid-lifetime"> <properties> <help>Time in seconds that the route will remain valid</help> <completionHelp> <list>infinity</list> </completionHelp> <valueHelp> <format>u32:1-4294967295</format> <description>Time in seconds that the route will remain valid</description> </valueHelp> <valueHelp> <format>infinity</format> <description>Route will remain preferred forever</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> <regex>(infinity)</regex> </constraint> </properties> <defaultValue>1800</defaultValue> </leafNode> <leafNode name="route-preference"> <properties> <help>Preference associated with the route,</help> <completionHelp> <list>low medium high</list> </completionHelp> <valueHelp> <format>low</format> <description>Route has low preference</description> </valueHelp> <valueHelp> <format>medium</format> <description>Route has medium preference</description> </valueHelp> <valueHelp> <format>high</format> <description>Route has high preference</description> </valueHelp> <constraint> <regex>(low|medium|high)</regex> </constraint> <constraintErrorMessage>Route preference must be low, medium or high</constraintErrorMessage> </properties> <defaultValue>medium</defaultValue> </leafNode> <leafNode name="no-remove-route"> <properties> <help>Do not announce this route with a zero second lifetime upon shutdown</help> <valueless/> </properties> </leafNode> </children> </tagNode> <tagNode name="nat64prefix"> <properties> <help>NAT64 prefix included in the router advertisements</help> <valueHelp> <format>ipv6net</format> <description>IPv6 prefix to be advertized</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> <children> <leafNode name="valid-lifetime"> <properties> <help>Time in seconds that the prefix will remain valid</help> <completionHelp> <list>infinity</list> </completionHelp> <valueHelp> <format>u32:4-65528</format> <description>Time in seconds that the prefix will remain valid</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 4-65528"/> </constraint> </properties> <defaultValue>65528</defaultValue> </leafNode> </children> </tagNode> <tagNode name="prefix"> <properties> <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help> <valueHelp> <format>ipv6net</format> <description>IPv6 prefix to be advertized</description> </valueHelp> <constraint> <validator name="ipv6-prefix"/> </constraint> </properties> <children> <leafNode name="no-autonomous-flag"> <properties> <help>Prefix can not be used for stateless address auto-configuration</help> <valueless/> </properties> </leafNode> <leafNode name="no-on-link-flag"> <properties> <help>Prefix can not be used for on-link determination</help> <valueless/> </properties> </leafNode> <leafNode name="deprecate-prefix"> <properties> <help>Upon shutdown, this option will deprecate the prefix by announcing it in the shutdown RA</help> <valueless/> </properties> </leafNode> <leafNode name="decrement-lifetime"> <properties> <help>Lifetime is decremented by the number of seconds since the last RA - use in conjunction with a DHCPv6-PD prefix</help> <valueless/> </properties> </leafNode> <leafNode name="preferred-lifetime"> <properties> <help>Time in seconds that the prefix will remain preferred</help> <completionHelp> <list>infinity</list> </completionHelp> <valueHelp> <format>u32</format> <description>Time in seconds that the prefix will remain preferred</description> </valueHelp> <valueHelp> <format>infinity</format> <description>Prefix will remain preferred forever</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> <regex>(infinity)</regex> </constraint> </properties> <defaultValue>14400</defaultValue> </leafNode> <leafNode name="valid-lifetime"> <properties> <help>Time in seconds that the prefix will remain valid</help> <completionHelp> <list>infinity</list> </completionHelp> <valueHelp> <format>u32:1-4294967295</format> <description>Time in seconds that the prefix will remain valid</description> </valueHelp> <valueHelp> <format>infinity</format> <description>Prefix will remain preferred forever</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4294967295"/> <regex>(infinity)</regex> </constraint> </properties> <defaultValue>2592000</defaultValue> </leafNode> </children> </tagNode> <leafNode name="source-address"> <properties> <help>Use IPv6 address as source address. Useful with VRRP.</help> <valueHelp> <format>ipv6</format> <description>IPv6 address to be advertized (must be configured on interface)</description> </valueHelp> <constraint> <validator name="ipv6-address"/> </constraint> <multi/> </properties> </leafNode> <leafNode name="reachable-time"> <properties> <help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help> <valueHelp> <format>u32:0</format> <description>Reachable Time unspecified by this router</description> </valueHelp> <valueHelp> <format>u32:1-3600000</format> <description>Reachable Time value in RAs (in milliseconds)</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 1-3600000"/> </constraint> <constraintErrorMessage>Reachable time must be 0 or between 1 and 3600000 milliseconds</constraintErrorMessage> </properties> <defaultValue>0</defaultValue> </leafNode> <leafNode name="retrans-timer"> <properties> <help>Time in milliseconds between retransmitted Neighbor Solicitation messages</help> <valueHelp> <format>u32:0</format> <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description> </valueHelp> <valueHelp> <format>u32:1-4294967295</format> <description>Minimum interval in milliseconds</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-0 --range 1-4294967295"/> </constraint> <constraintErrorMessage>Retransmit interval must be 0 or between 1 and 4294967295 milliseconds</constraintErrorMessage> </properties> <defaultValue>0</defaultValue> </leafNode> <leafNode name="no-send-advert"> <properties> <help>Do not send router adverts</help> <valueless/> </properties> </leafNode> + <leafNode name="no-send-interval"> + <properties> + <help>Do not send Advertisement Interval option in RAs</help> + <valueless/> + </properties> + </leafNode> </children> </tagNode> </children> </node> </children> </node> </interfaceDefinition> diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py index d1ff25a58..6dbb6add4 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -1,228 +1,257 @@ #!/usr/bin/env python3 # # Copyright (C) 2019-2022 VyOS maintainers and contributors # # 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 re import unittest from vyos.configsession import ConfigSessionError from base_vyostest_shim import VyOSUnitTestSHIM from vyos.utils.file import read_file from vyos.utils.process import process_named_running PROCESS_NAME = 'radvd' RADVD_CONF = '/run/radvd/radvd.conf' interface = 'eth1' base_path = ['service', 'router-advert', 'interface', interface] address_base = ['interfaces', 'ethernet', interface, 'address'] prefix = '::/64' def get_config_value(key): tmp = read_file(RADVD_CONF) tmp = re.findall(r'\n?{}\s+(.*)'.format(key), tmp) return tmp[0].split()[0].replace(';','') class TestServiceRADVD(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestServiceRADVD, cls).setUpClass() # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, ['service', 'router-advert']) cls.cli_set(cls, address_base + ['2001:db8::1/64']) @classmethod def tearDownClass(cls): cls.cli_delete(cls, address_base) super(TestServiceRADVD, cls).tearDownClass() def tearDown(self): # Check for running process self.assertTrue(process_named_running(PROCESS_NAME)) self.cli_delete(base_path) self.cli_commit() # Check for no longer running process self.assertFalse(process_named_running(PROCESS_NAME)) def test_common(self): self.cli_set(base_path + ['prefix', prefix, 'no-on-link-flag']) self.cli_set(base_path + ['prefix', prefix, 'no-autonomous-flag']) self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['other-config-flag']) # commit changes self.cli_commit() # verify values tmp = get_config_value('interface') self.assertEqual(tmp, interface) tmp = get_config_value('prefix') self.assertEqual(tmp, prefix) tmp = get_config_value('AdvOtherConfigFlag') self.assertEqual(tmp, 'on') # this is a default value tmp = get_config_value('AdvRetransTimer') self.assertEqual(tmp, '0') # this is a default value tmp = get_config_value('AdvCurHopLimit') self.assertEqual(tmp, '64') # this is a default value tmp = get_config_value('AdvDefaultPreference') self.assertEqual(tmp, 'medium') tmp = get_config_value('AdvAutonomous') self.assertEqual(tmp, 'off') # this is a default value tmp = get_config_value('AdvValidLifetime') self.assertEqual(tmp, 'infinity') # this is a default value tmp = get_config_value('AdvPreferredLifetime') self.assertEqual(tmp, '14400') tmp = get_config_value('AdvOnLink') self.assertEqual(tmp, 'off') tmp = get_config_value('DeprecatePrefix') self.assertEqual(tmp, 'off') tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'off') def test_dns(self): nameserver = ['2001:db8::1', '2001:db8::2'] dnssl = ['vyos.net', 'vyos.io'] ns_lifetime = '599' self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['other-config-flag']) for ns in nameserver: self.cli_set(base_path + ['name-server', ns]) for sl in dnssl: self.cli_set(base_path + ['dnssl', sl]) self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) # The value, if not 0, must be at least interval max (defaults to 600). with self.assertRaises(ConfigSessionError): self.cli_commit() ns_lifetime = '600' self.cli_set(base_path + ['name-server-lifetime', ns_lifetime]) # commit changes self.cli_commit() config = read_file(RADVD_CONF) tmp = 'RDNSS ' + ' '.join(nameserver) + ' {' self.assertIn(tmp, config) tmp = f'AdvRDNSSLifetime {ns_lifetime};' self.assertIn(tmp, config) tmp = 'DNSSL ' + ' '.join(dnssl) + ' {' self.assertIn(tmp, config) def test_deprecate_prefix(self): self.cli_set(base_path + ['prefix', prefix, 'valid-lifetime', 'infinity']) self.cli_set(base_path + ['prefix', prefix, 'deprecate-prefix']) self.cli_set(base_path + ['prefix', prefix, 'decrement-lifetime']) # commit changes self.cli_commit() tmp = get_config_value('DeprecatePrefix') self.assertEqual(tmp, 'on') tmp = get_config_value('DecrementLifetimes') self.assertEqual(tmp, 'on') def test_route(self): route = '2001:db8:1000::/64' self.cli_set(base_path + ['prefix', prefix]) self.cli_set(base_path + ['route', route]) # commit changes self.cli_commit() config = read_file(RADVD_CONF) tmp = f'route {route}' + ' {' self.assertIn(tmp, config) self.assertIn('AdvRouteLifetime 1800;', config) self.assertIn('AdvRoutePreference medium;', config) self.assertIn('RemoveRoute on;', config) def test_rasrcaddress(self): ra_src = ['fe80::1', 'fe80::2'] self.cli_set(base_path + ['prefix', prefix]) for src in ra_src: self.cli_set(base_path + ['source-address', src]) # commit changes self.cli_commit() config = read_file(RADVD_CONF) self.assertIn('AdvRASrcAddress {', config) for src in ra_src: self.assertIn(f' {src};', config) def test_nat64prefix(self): nat64prefix = '64:ff9b::/96' nat64prefix_invalid = '64:ff9b::/44' self.cli_set(base_path + ['nat64prefix', nat64prefix]) # and another invalid prefix # Invalid NAT64 prefix length for "2001:db8::/34", can only be one of: # /32, /40, /48, /56, /64, /96 self.cli_set(base_path + ['nat64prefix', nat64prefix_invalid]) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['nat64prefix', nat64prefix_invalid]) # NAT64 valid-lifetime must not be smaller then "interval max" self.cli_set(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime', '500']) with self.assertRaises(ConfigSessionError): self.cli_commit() self.cli_delete(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime']) # commit changes self.cli_commit() config = read_file(RADVD_CONF) tmp = f'nat64prefix {nat64prefix}' + ' {' self.assertIn(tmp, config) self.assertIn('AdvValidLifetime 65528;', config) # default + def test_advsendadvert_advintervalopt(self): + ra_src = ['fe80::1', 'fe80::2'] + + self.cli_set(base_path + ['prefix', prefix]) + self.cli_set(base_path + ['no-send-advert']) + # commit changes + self.cli_commit() + + # Verify generated configuration + config = read_file(RADVD_CONF) + tmp = get_config_value('AdvSendAdvert') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('AdvIntervalOpt') + self.assertEqual(tmp, 'on') + + self.cli_set(base_path + ['no-send-interval']) + # commit changes + self.cli_commit() + + # Verify generated configuration + config = read_file(RADVD_CONF) + tmp = get_config_value('AdvSendAdvert') + self.assertEqual(tmp, 'off') + + tmp = get_config_value('AdvIntervalOpt') + self.assertEqual(tmp, 'off') + + if __name__ == '__main__': unittest.main(verbosity=2)