From 45d472c8782f927980044eaba0d49421e91fc0c8 Mon Sep 17 00:00:00 2001
From: sarthurdev <965089+sarthurdev@users.noreply.github.com>
Date: Sat, 13 Jan 2024 00:28:01 +0100
Subject: dhcpv6: T3316: Move options to separate node and extend scopes
* Also migrate `address-range` to `range` tag node for consistency with dhcpv4 server syntax
---
interface-definitions/include/dhcp/option-v6.xml.i | 110 ++++++++++++++++
interface-definitions/service_dhcpv6-server.xml.in | 143 ++++-----------------
python/vyos/kea.py | 43 +++----
python/vyos/template.py | 7 +-
smoketest/configs/basic-vyos | 7 +-
.../scripts/cli/test_service_dhcpv6-server.py | 26 ++--
src/conf_mode/service_dhcpv6-server.py | 44 ++++---
src/migration-scripts/dhcpv6-server/3-to-4 | 35 +++++
src/op_mode/dhcp.py | 9 +-
9 files changed, 240 insertions(+), 184 deletions(-)
create mode 100644 interface-definitions/include/dhcp/option-v6.xml.i
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 @@
+
+
+
+ DHCPv6 option
+
+
+ #include
+ #include
+ #include
+
+
+ NIS domain name for client to use
+
+ #include
+
+ Invalid NIS domain name
+
+
+
+
+ IPv6 address of a NIS Server
+
+ ipv6
+ IPv6 address of NIS server
+
+
+
+
+
+
+
+
+
+ NIS+ domain name for client to use
+
+ #include
+
+ Invalid NIS+ domain name. May only contain letters, numbers and .-_
+
+
+
+
+ IPv6 address of a NIS+ Server
+
+ ipv6
+ IPv6 address of NIS+ server
+
+
+
+
+
+
+
+
+
+ IPv6 address of SIP server
+
+ ipv6
+ IPv6 address of SIP server
+
+
+ hostname
+ FQDN of SIP server
+
+
+
+
+
+
+
+
+
+
+ IPv6 address of an SNTP server for client to use
+
+
+
+
+
+
+
+
+ Vendor Specific Options
+
+
+
+
+ Cisco specific parameters
+
+
+
+
+ TFTP server name
+
+ ipv6
+ TFTP server IPv6 address
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in
index 6934ceeec..4c1a28016 100644
--- a/interface-definitions/service_dhcpv6-server.xml.in
+++ b/interface-definitions/service_dhcpv6-server.xml.in
@@ -89,11 +89,17 @@
-
+ #include
+
Parameters setting ranges for assigning IPv6 addresses
+
+ #include
+
+ Invalid range name, may only be alphanumeric, dot and hyphen
+ #include
IPv6 prefix defining range of addresses to assign
@@ -104,10 +110,9 @@
-
-
+
First in range of consecutive IPv6 addresses to assign
@@ -118,25 +123,21 @@
-
-
-
- Last in range of consecutive IPv6 addresses
-
- ipv6
- IPv6 address
-
-
-
-
-
-
-
-
+
+
+
+ Last in range of consecutive IPv6 addresses
+
+ ipv6
+ IPv6 address
+
+
+
+
+
+
-
- #include
- #include
+
Parameters relating to the lease time
@@ -180,51 +181,6 @@
- #include
-
-
- NIS domain name for client to use
-
- #include
-
- Invalid NIS domain name
-
-
-
-
- IPv6 address of a NIS Server
-
- ipv6
- IPv6 address of NIS server
-
-
-
-
-
-
-
-
-
- NIS+ domain name for client to use
-
- #include
-
- Invalid NIS+ domain name. May only contain letters, numbers and .-_
-
-
-
-
- IPv6 address of a NIS+ Server
-
- ipv6
- IPv6 address of NIS+ server
-
-
-
-
-
-
-
Parameters relating to IPv6 prefix delegation
@@ -272,33 +228,6 @@
-
-
- IPv6 address of SIP server
-
- ipv6
- IPv6 address of SIP server
-
-
- hostname
- FQDN of SIP server
-
-
-
-
-
-
-
-
-
-
- IPv6 address of an SNTP server for client to use
-
-
-
-
-
-
Hostname for static mapping reservation
@@ -308,6 +237,7 @@
Invalid static mapping hostname
+ #include
#include
#include
#include
@@ -349,33 +279,6 @@
-
-
- Vendor Specific Options
-
-
-
-
- Cisco specific parameters
-
-
-
-
- TFTP server name
-
- ipv6
- TFTP server IPv6 address
-
-
-
-
-
-
-
-
-
-
-
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')
--
cgit v1.2.3