diff options
-rw-r--r-- | data/templates/dhcp-server/kea-dhcp6.conf.j2 | 4 | ||||
-rw-r--r-- | interface-definitions/include/dhcp/option-v6.xml.i | 110 | ||||
-rw-r--r-- | interface-definitions/service_dhcpv6-server.xml.in | 144 | ||||
-rw-r--r-- | python/vyos/kea.py | 43 | ||||
-rw-r--r-- | python/vyos/template.py | 7 | ||||
-rw-r--r-- | smoketest/configs/basic-vyos | 7 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_dhcpv6-server.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/service_dhcpv6-server.py | 44 | ||||
-rwxr-xr-x | src/migration-scripts/dhcpv6-server/3-to-4 | 35 | ||||
-rwxr-xr-x | src/op_mode/dhcp.py | 9 |
10 files changed, 245 insertions, 184 deletions
diff --git a/data/templates/dhcp-server/kea-dhcp6.conf.j2 b/data/templates/dhcp-server/kea-dhcp6.conf.j2 index 3ce4e6370..3ab21551b 100644 --- a/data/templates/dhcp-server/kea-dhcp6.conf.j2 +++ b/data/templates/dhcp-server/kea-dhcp6.conf.j2 @@ -1,7 +1,11 @@ { "Dhcp6": { "interfaces-config": { +{% if listen_interface is vyos_defined %} + "interfaces": {{ listen_interface | tojson }}, +{% else %} "interfaces": [ "*" ], +{% endif %} "service-sockets-max-retries": 5, "service-sockets-retry-wait-time": 5000 }, diff --git a/interface-definitions/include/dhcp/option-v6.xml.i b/interface-definitions/include/dhcp/option-v6.xml.i new file mode 100644 index 000000000..1df0c3934 --- /dev/null +++ b/interface-definitions/include/dhcp/option-v6.xml.i @@ -0,0 +1,110 @@ +<!-- include start from dhcp/option-v6.xml.i --> +<node name="option"> + <properties> + <help>DHCPv6 option</help> + </properties> + <children> + #include <include/dhcp/captive-portal.xml.i> + #include <include/dhcp/domain-search.xml.i> + #include <include/name-server-ipv6.xml.i> + <leafNode name="nis-domain"> + <properties> + <help>NIS domain name for client to use</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid NIS domain name</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="nis-server"> + <properties> + <help>IPv6 address of a NIS Server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of NIS server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="nisplus-domain"> + <properties> + <help>NIS+ domain name for client to use</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid NIS+ domain name. May only contain letters, numbers and .-_</constraintErrorMessage> + </properties> + </leafNode> + <leafNode name="nisplus-server"> + <properties> + <help>IPv6 address of a NIS+ Server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of NIS+ server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="sip-server"> + <properties> + <help>IPv6 address of SIP server</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address of SIP server</description> + </valueHelp> + <valueHelp> + <format>hostname</format> + <description>FQDN of SIP server</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + <validator name="fqdn"/> + </constraint> + <multi/> + </properties> + </leafNode> + <leafNode name="sntp-server"> + <properties> + <help>IPv6 address of an SNTP server for client to use</help> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + <node name="vendor-option"> + <properties> + <help>Vendor Specific Options</help> + </properties> + <children> + <node name="cisco"> + <properties> + <help>Cisco specific parameters</help> + </properties> + <children> + <leafNode name="tftp-server"> + <properties> + <help>TFTP server name</help> + <valueHelp> + <format>ipv6</format> + <description>TFTP server IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + <multi/> + </properties> + </leafNode> + </children> + </node> + </children> + </node> + </children> +</node> +<!-- include end --> diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in index 6934ceeec..07cbfc85d 100644 --- a/interface-definitions/service_dhcpv6-server.xml.in +++ b/interface-definitions/service_dhcpv6-server.xml.in @@ -9,6 +9,7 @@ </properties> <children> #include <include/generic-disable-node.xml.i> + #include <include/listen-interface-multi-broadcast.xml.i> <node name="global-parameters"> <properties> <help>Additional global parameters for DHCPv6 server</help> @@ -89,11 +90,17 @@ </constraint> </properties> <children> - <node name="address-range"> + #include <include/dhcp/option-v6.xml.i> + <tagNode name="range"> <properties> <help>Parameters setting ranges for assigning IPv6 addresses</help> + <constraint> + #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> + </constraint> + <constraintErrorMessage>Invalid range name, may only be alphanumeric, dot and hyphen</constraintErrorMessage> </properties> <children> + #include <include/dhcp/option-v6.xml.i> <leafNode name="prefix"> <properties> <help>IPv6 prefix defining range of addresses to assign</help> @@ -104,10 +111,9 @@ <constraint> <validator name="ipv6-prefix"/> </constraint> - <multi/> </properties> </leafNode> - <tagNode name="start"> + <leafNode name="start"> <properties> <help>First in range of consecutive IPv6 addresses to assign</help> <valueHelp> @@ -118,25 +124,21 @@ <validator name="ipv6-address"/> </constraint> </properties> - <children> - <leafNode name="stop"> - <properties> - <help>Last in range of consecutive IPv6 addresses</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - </properties> - </leafNode> - </children> - </tagNode> + </leafNode> + <leafNode name="stop"> + <properties> + <help>Last in range of consecutive IPv6 addresses</help> + <valueHelp> + <format>ipv6</format> + <description>IPv6 address</description> + </valueHelp> + <constraint> + <validator name="ipv6-address"/> + </constraint> + </properties> + </leafNode> </children> - </node> - #include <include/dhcp/captive-portal.xml.i> - #include <include/dhcp/domain-search.xml.i> + </tagNode> <node name="lease-time"> <properties> <help>Parameters relating to the lease time</help> @@ -180,51 +182,6 @@ </leafNode> </children> </node> - #include <include/name-server-ipv6.xml.i> - <leafNode name="nis-domain"> - <properties> - <help>NIS domain name for client to use</help> - <constraint> - #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> - </constraint> - <constraintErrorMessage>Invalid NIS domain name</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="nis-server"> - <properties> - <help>IPv6 address of a NIS Server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of NIS server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="nisplus-domain"> - <properties> - <help>NIS+ domain name for client to use</help> - <constraint> - #include <include/constraint/alpha-numeric-hyphen-underscore-dot.xml.i> - </constraint> - <constraintErrorMessage>Invalid NIS+ domain name. May only contain letters, numbers and .-_</constraintErrorMessage> - </properties> - </leafNode> - <leafNode name="nisplus-server"> - <properties> - <help>IPv6 address of a NIS+ Server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of NIS+ server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> <node name="prefix-delegation"> <properties> <help>Parameters relating to IPv6 prefix delegation</help> @@ -272,33 +229,6 @@ </tagNode> </children> </node> - <leafNode name="sip-server"> - <properties> - <help>IPv6 address of SIP server</help> - <valueHelp> - <format>ipv6</format> - <description>IPv6 address of SIP server</description> - </valueHelp> - <valueHelp> - <format>hostname</format> - <description>FQDN of SIP server</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - <validator name="fqdn"/> - </constraint> - <multi/> - </properties> - </leafNode> - <leafNode name="sntp-server"> - <properties> - <help>IPv6 address of an SNTP server for client to use</help> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> <tagNode name="static-mapping"> <properties> <help>Hostname for static mapping reservation</help> @@ -308,6 +238,7 @@ <constraintErrorMessage>Invalid static mapping hostname</constraintErrorMessage> </properties> <children> + #include <include/dhcp/option-v6.xml.i> #include <include/generic-disable-node.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/duid.xml.i> @@ -349,33 +280,6 @@ </constraint> </properties> </leafNode> - <node name="vendor-option"> - <properties> - <help>Vendor Specific Options</help> - </properties> - <children> - <node name="cisco"> - <properties> - <help>Cisco specific parameters</help> - </properties> - <children> - <leafNode name="tftp-server"> - <properties> - <help>TFTP server name</help> - <valueHelp> - <format>ipv6</format> - <description>TFTP server IPv6 address</description> - </valueHelp> - <constraint> - <validator name="ipv6-address"/> - </constraint> - <multi/> - </properties> - </leafNode> - </children> - </node> - </children> - </node> </children> </tagNode> </children> diff --git a/python/vyos/kea.py b/python/vyos/kea.py index aa4fb7ae5..fb5afc2ce 100644 --- a/python/vyos/kea.py +++ b/python/vyos/kea.py @@ -175,16 +175,6 @@ def kea_parse_subnet(subnet, config): def kea6_parse_options(config): options = [] - if 'common_options' in config: - common_opt = config['common_options'] - - for node, option_name in kea6_options.items(): - if node not in common_opt: - continue - - value = ", ".join(common_opt[node]) if isinstance(common_opt[node], list) else common_opt[node] - options.append({'name': option_name, 'data': value}) - for node, option_name in kea6_options.items(): if node not in config: continue @@ -218,20 +208,27 @@ def kea6_parse_options(config): def kea6_parse_subnet(subnet, config): out = {'subnet': subnet, 'id': int(config['subnet_id'])} - options = kea6_parse_options(config) - if 'address_range' in config: - addr_range = config['address_range'] + if 'option' in config: + out['option-data'] = kea6_parse_options(config['option']) + + if 'range' in config: pools = [] + for num, range_config in config['range'].items(): + pool = {} - if 'prefix' in addr_range: - for prefix in addr_range['prefix']: - pools.append({'pool': prefix}) + if 'prefix' in range_config: + pool['pool'] = range_config['prefix'] - if 'start' in addr_range: - for start, range_conf in addr_range['start'].items(): - stop = range_conf['stop'] - pools.append({'pool': f'{start} - {stop}'}) + if 'start' in range_config: + start = range_config['start'] + stop = range_config['stop'] + pool['pool'] = f'{start} - {stop}' + + if 'option' in range_config: + pool['option-data'] = kea6_parse_options(range_config['option']) + + pools.append(pool) out['pools'] = pools @@ -278,13 +275,13 @@ def kea6_parse_subnet(subnet, config): if 'ipv6_prefix' in host_config: reservation['prefixes'] = [ host_config['ipv6_prefix'] ] + if 'option' in host_config: + reservation['option-data'] = kea6_parse_options(host_config['option']) + reservations.append(reservation) out['reservations'] = reservations - if options: - out['option-data'] = options - return out def kea_parse_leases(lease_path): diff --git a/python/vyos/template.py b/python/vyos/template.py index 1368f1f61..456239568 100644 --- a/python/vyos/template.py +++ b/python/vyos/template.py @@ -894,7 +894,9 @@ def kea6_shared_network_json(shared_networks): 'name': name, 'subnet6': [] } - options = kea6_parse_options(config) + + if 'common_options' in config: + network['option-data'] = kea6_parse_options(config['common_options']) if 'interface' in config: network['interface'] = config['interface'] @@ -903,9 +905,6 @@ def kea6_shared_network_json(shared_networks): for subnet, subnet_config in config['subnet'].items(): network['subnet6'].append(kea6_parse_subnet(subnet, subnet_config)) - if options: - network['option-data'] = options - out.append(network) return dumps(out, indent=4) diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos index fca4964bf..c42f14841 100644 --- a/smoketest/configs/basic-vyos +++ b/smoketest/configs/basic-vyos @@ -95,10 +95,15 @@ service { shared-network-name LAN6 { subnet fe88::/56 { address-range { - prefix fe88::/56 { + prefix fe88::/60 { temporary } + start fe88:0000:0000:fe:: { + stop fe88:0000:0000:ff:: + } } + domain-search vyos.net + name-server fe88::1 prefix-delegation { start fe88:0000:0000:0001:: { prefix-length 64 diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py index fcbfeb7be..dcce30f55 100755 --- a/smoketest/scripts/cli/test_service_dhcpv6-server.py +++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py @@ -104,24 +104,25 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['preference', preference]) self.cli_set(pool + ['subnet-id', '1']) # we use the first subnet IP address as default gateway - self.cli_set(pool + ['name-server', dns_1]) - self.cli_set(pool + ['name-server', dns_2]) - self.cli_set(pool + ['name-server', dns_2]) self.cli_set(pool + ['lease-time', 'default', lease_time]) self.cli_set(pool + ['lease-time', 'maximum', max_lease_time]) self.cli_set(pool + ['lease-time', 'minimum', min_lease_time]) - self.cli_set(pool + ['nis-domain', domain]) - self.cli_set(pool + ['nisplus-domain', domain]) - self.cli_set(pool + ['sip-server', sip_server]) - self.cli_set(pool + ['sntp-server', sntp_server]) - self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) + self.cli_set(pool + ['option', 'name-server', dns_1]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'name-server', dns_2]) + self.cli_set(pool + ['option', 'nis-domain', domain]) + self.cli_set(pool + ['option', 'nisplus-domain', domain]) + self.cli_set(pool + ['option', 'sip-server', sip_server]) + self.cli_set(pool + ['option', 'sntp-server', sntp_server]) + self.cli_set(pool + ['range', '1', 'start', range_start]) + self.cli_set(pool + ['range', '1', 'stop', range_stop]) for server in nis_servers: - self.cli_set(pool + ['nis-server', server]) - self.cli_set(pool + ['nisplus-server', server]) + self.cli_set(pool + ['option', 'nis-server', server]) + self.cli_set(pool + ['option', 'nisplus-server', server]) for search in search_domains: - self.cli_set(pool + ['domain-search', search]) + self.cli_set(pool + ['option', 'domain-search', search]) client_base = 1 for client in ['client1', 'client2', 'client3']: @@ -217,7 +218,8 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase): pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet] self.cli_set(pool + ['subnet-id', '1']) - self.cli_set(pool + ['address-range', 'start', range_start, 'stop', range_stop]) + self.cli_set(pool + ['range', '1', 'start', range_start]) + self.cli_set(pool + ['range', '1', 'stop', range_stop]) self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len]) self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len]) diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py index 7cd801cdd..214531904 100755 --- a/src/conf_mode/service_dhcpv6-server.py +++ b/src/conf_mode/service_dhcpv6-server.py @@ -81,26 +81,29 @@ def verify(dhcpv6): subnet_ids.append(subnet_config['subnet_id']) - if 'address_range' in subnet_config: - if 'start' in subnet_config['address_range']: - range6_start = [] - range6_stop = [] - for start, start_config in subnet_config['address_range']['start'].items(): - if 'stop' not in start_config: - raise ConfigError(f'address-range stop address for start "{start}" is not defined!') - stop = start_config['stop'] + if 'range' in subnet_config: + range6_start = [] + range6_stop = [] + + for num, range_config in subnet_config['range'].items(): + if 'start' in range_config: + start = range_config['start'] + + if 'stop' not in range_config: + raise ConfigError(f'Range stop address for start "{start}" is not defined!') + stop = range_config['stop'] # Start address must be inside network if not ip_address(start) in ip_network(subnet): - raise ConfigError(f'address-range start address "{start}" is not in subnet "{subnet}"!') + raise ConfigError(f'Range start address "{start}" is not in subnet "{subnet}"!') # Stop address must be inside network if not ip_address(stop) in ip_network(subnet): - raise ConfigError(f'address-range stop address "{stop}" is not in subnet "{subnet}"!') + raise ConfigError(f'Range stop address "{stop}" is not in subnet "{subnet}"!') # Stop address must be greater or equal to start address if not ip_address(stop) >= ip_address(start): - raise ConfigError(f'address-range stop address "{stop}" must be greater then or equal ' \ + raise ConfigError(f'Range stop address "{stop}" must be greater then or equal ' \ f'to the range start address "{start}"!') # DHCPv6 range start address must be unique - two ranges can't @@ -108,6 +111,7 @@ def verify(dhcpv6): if start in range6_start: raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool start address "{start}" defined multipe times!') + range6_start.append(start) # DHCPv6 range stop address must be unique - two ranges can't @@ -115,12 +119,14 @@ def verify(dhcpv6): if stop in range6_stop: raise ConfigError(f'Conflicting DHCPv6 lease range: '\ f'Pool stop address "{stop}" defined multipe times!') + range6_stop.append(stop) - if 'prefix' in subnet_config: - for prefix in subnet_config['prefix']: - if ip_network(prefix) not in ip_network(subnet): - raise ConfigError(f'address-range prefix "{prefix}" is not in subnet "{subnet}""') + if 'prefix' in range_config: + prefix = range_config['prefix'] + + if not ip_network(prefix).subnet_of(ip_network(subnet)): + raise ConfigError(f'Range prefix "{prefix}" is not in subnet "{subnet}"') # Prefix delegation sanity checks if 'prefix_delegation' in subnet_config: @@ -151,13 +157,15 @@ def verify(dhcpv6): raise ConfigError(f'Either MAC address or Client identifier (DUID) is required for ' f'static mapping "{mapping}" within shared-network "{network}, {subnet}"!') - if 'vendor_option' in subnet_config: - if len(dict_search('vendor_option.cisco.tftp_server', subnet_config)) > 2: - raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') + if 'option' in subnet_config: + if 'vendor_option' in subnet_config['option']: + if len(dict_search('option.vendor_option.cisco.tftp_server', subnet_config)) > 2: + raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!') # Subnets must be unique if subnet in subnets: raise ConfigError(f'DHCPv6 subnets must be unique! Subnet {subnet} defined multiple times!') + subnets.append(subnet) # DHCPv6 requires at least one configured address range or one static mapping diff --git a/src/migration-scripts/dhcpv6-server/3-to-4 b/src/migration-scripts/dhcpv6-server/3-to-4 index c065e3d43..4747ebd60 100755 --- a/src/migration-scripts/dhcpv6-server/3-to-4 +++ b/src/migration-scripts/dhcpv6-server/3-to-4 @@ -16,6 +16,8 @@ # T3316: # - Add subnet IDs to existing subnets +# - Move options to option node +# - Migrate address-range to range tagNode import sys import re @@ -37,6 +39,10 @@ if not config.exists(base): # Nothing to do sys.exit(0) +option_nodes = ['captive-portal', 'domain-search', 'name-server', + 'nis-domain', 'nis-server', 'nisplus-domain', 'nisplus-server', + 'sip-server', 'sntp-server', 'vendor-option'] + subnet_id = 1 for network in config.list_nodes(base): @@ -44,6 +50,35 @@ for network in config.list_nodes(base): for subnet in config.list_nodes(base + [network, 'subnet']): base_subnet = base + [network, 'subnet', subnet] + if config.exists(base_subnet + ['address-range']): + config.set(base_subnet + ['range']) + config.set_tag(base_subnet + ['range']) + + range_id = 1 + + if config.exists(base_subnet + ['address-range', 'prefix']): + for prefix in config.return_values(base_subnet + ['address-range', 'prefix']): + config.set(base_subnet + ['range', range_id, 'prefix'], value=prefix) + + range_id += 1 + + if config.exists(base_subnet + ['address-range', 'start']): + for start in config.list_nodes(base_subnet + ['address-range', 'start']): + stop = config.return_value(base_subnet + ['address-range', 'start', start, 'stop']) + + config.set(base_subnet + ['range', range_id, 'start'], value=start) + config.set(base_subnet + ['range', range_id, 'stop'], value=stop) + + range_id += 1 + + config.delete(base_subnet + ['address-range']) + + for option in option_nodes: + if config.exists(base_subnet + [option]): + config.set(base_subnet + ['option']) + config.copy(base_subnet + [option], base_subnet + ['option', option]) + config.delete(base_subnet + [option]) + config.set(base_subnet + ['subnet-id'], value=subnet_id) subnet_id += 1 diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py index 02f4d5bbb..a64acec31 100755 --- a/src/op_mode/dhcp.py +++ b/src/op_mode/dhcp.py @@ -194,14 +194,11 @@ def _get_pool_size(pool, family='inet'): size = 0 subnets = config.list_nodes(f'{base} subnet') for subnet in subnets: - if family == 'inet6': - ranges = config.list_nodes(f'{base} subnet {subnet} address-range start') - else: - ranges = config.list_nodes(f'{base} subnet {subnet} range') + ranges = config.list_nodes(f'{base} subnet {subnet} range') for range in ranges: if family == 'inet6': - start = config.list_nodes(f'{base} subnet {subnet} address-range start')[0] - stop = config.value(f'{base} subnet {subnet} address-range start {start} stop') + start = config.value(f'{base} subnet {subnet} range {range} start') + stop = config.value(f'{base} subnet {subnet} range {range} stop') else: start = config.value(f'{base} subnet {subnet} range {range} start') stop = config.value(f'{base} subnet {subnet} range {range} stop') |