diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2 index 4ef4751dd..97180d164 100644 --- a/data/templates/router-advert/radvd.conf.j2 +++ b/data/templates/router-advert/radvd.conf.j2 @@ -1,78 +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' }}; 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 16c29022d..166a4a0cf 100644 --- a/interface-definitions/service_router-advert.xml.in +++ b/interface-definitions/service_router-advert.xml.in @@ -1,369 +1,399 @@ <?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> </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 5fc2019fd..d1ff25a58 100755 --- a/smoketest/scripts/cli/test_service_router-advert.py +++ b/smoketest/scripts/cli/test_service_router-advert.py @@ -1,200 +1,228 @@ #!/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 if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py index dbb47de4e..88d767bb8 100755 --- a/src/conf_mode/service_router-advert.py +++ b/src/conf_mode/service_router-advert.py @@ -1,113 +1,125 @@ #!/usr/bin/env python3 # -# Copyright (C) 2018-2022 VyOS maintainers and contributors +# Copyright (C) 2018-2024 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 os from sys import exit +from ipaddress import IPv6Network + from vyos.base import Warning from vyos.config import Config from vyos.template import render from vyos.utils.process import call from vyos import ConfigError from vyos import airbag airbag.enable() config_file = r'/run/radvd/radvd.conf' def get_config(config=None): if config: conf = config else: conf = Config() base = ['service', 'router-advert'] rtradv = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True) return rtradv def verify(rtradv): if not rtradv: return None if 'interface' not in rtradv: return None for interface, interface_config in rtradv['interface'].items(): - if 'prefix' in interface: + interval_max = int(interface_config['interval']['max']) + + if 'prefix' in interface_config: for prefix, prefix_config in interface_config['prefix'].items(): valid_lifetime = prefix_config['valid_lifetime'] if valid_lifetime == 'infinity': valid_lifetime = 4294967295 preferred_lifetime = prefix_config['preferred_lifetime'] if preferred_lifetime == 'infinity': preferred_lifetime = 4294967295 if not (int(valid_lifetime) >= int(preferred_lifetime)): raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime') + if 'nat64prefix' in interface_config: + nat64_supported_lengths = [32, 40, 48, 56, 64, 96] + for prefix, prefix_config in interface_config['nat64prefix'].items(): + if IPv6Network(prefix).prefixlen not in nat64_supported_lengths: + raise ConfigError(f'Invalid NAT64 prefix length for "{prefix}", can only be one of: /' + ', /'.join(nat64_supported_lengths)) + + if int(prefix_config['valid_lifetime']) < interval_max: + raise ConfigError(f'NAT64 valid-lifetime must not be smaller then "interval max" which is "{interval_max}"!') + if 'name_server' in interface_config: if len(interface_config['name_server']) > 3: raise ConfigError('No more then 3 IPv6 name-servers supported!') if 'name_server_lifetime' in interface_config: # man page states: # The maximum duration how long the RDNSS entries are used for name # resolution. A value of 0 means the nameserver must no longer be # used. The value, if not 0, must be at least MaxRtrAdvInterval. To # ensure stale RDNSS info gets removed in a timely fashion, this # should not be greater than 2*MaxRtrAdvInterval. lifetime = int(interface_config['name_server_lifetime']) - interval_max = int(interface_config['interval']['max']) if lifetime > 0: if lifetime < int(interval_max): raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!') if lifetime > 2* interval_max: Warning(f'RDNSS lifetime should not exceed "{2 * interval_max}" which is two times "interval max"!') return None def generate(rtradv): if not rtradv: return None render(config_file, 'router-advert/radvd.conf.j2', rtradv, permission=0o644) return None def apply(rtradv): systemd_service = 'radvd.service' if not rtradv: # bail out early - looks like removal from running config call(f'systemctl stop {systemd_service}') if os.path.exists(config_file): os.unlink(config_file) return None call(f'systemctl reload-or-restart {systemd_service}') return None if __name__ == '__main__': try: c = get_config() verify(c) generate(c) apply(c) except ConfigError as e: print(e) exit(1)