From ee494c3a1dbfca3457bcaffe89d45971348e4848 Mon Sep 17 00:00:00 2001
From: Simon <965089+sarthurdev@users.noreply.github.com>
Date: Thu, 11 Jan 2024 06:46:33 +0100
Subject: dhcp: dhcpv6: T3316: Add `subnet-id` so leases remain mapped to
entries in the lease file (#2796)
---
.../include/version/dhcpv6-server-version.xml.i | 2 +-
interface-definitions/service_dhcp-server.xml.in | 12 +++++
interface-definitions/service_dhcpv6-server.xml.in | 12 +++++
python/vyos/kea.py | 4 +-
smoketest/config-tests/basic-vyos | 9 ++--
smoketest/config-tests/dialup-router-medium-vpn | 9 ++--
smoketest/scripts/cli/test_service_dhcp-server.py | 14 ++++++
.../scripts/cli/test_service_dhcpv6-server.py | 8 ++--
src/conf_mode/service_dhcp-server.py | 9 ++++
src/conf_mode/service_dhcpv6-server.py | 9 ++++
src/migration-scripts/dhcp-server/8-to-9 | 14 ++++--
src/migration-scripts/dhcpv6-server/3-to-4 | 55 ++++++++++++++++++++++
12 files changed, 139 insertions(+), 18 deletions(-)
create mode 100755 src/migration-scripts/dhcpv6-server/3-to-4
diff --git a/interface-definitions/include/version/dhcpv6-server-version.xml.i b/interface-definitions/include/version/dhcpv6-server-version.xml.i
index cb026a54a..bfef27b77 100644
--- a/interface-definitions/include/version/dhcpv6-server-version.xml.i
+++ b/interface-definitions/include/version/dhcpv6-server-version.xml.i
@@ -1,3 +1,3 @@
-
+
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index 27485b6d4..5c9d4a360 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -200,6 +200,18 @@
#include
+
+
+ Unique ID mapped to leases in the lease file
+
+ u32
+ Unique subnet ID
+
+
+
+
+
+
diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in
index 6f7f3c1da..6934ceeec 100644
--- a/interface-definitions/service_dhcpv6-server.xml.in
+++ b/interface-definitions/service_dhcpv6-server.xml.in
@@ -337,6 +337,18 @@
+
+
+ Unique ID mapped to leases in the lease file
+
+ u32
+ Unique subnet ID
+
+
+
+
+
+
Vendor Specific Options
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 3d8cf3637..aa4fb7ae5 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -103,7 +103,7 @@ def kea_parse_options(config):
return options
def kea_parse_subnet(subnet, config):
- out = {'subnet': subnet}
+ out = {'subnet': subnet, 'id': int(config['subnet_id'])}
options = []
if 'option' in config:
@@ -217,7 +217,7 @@ def kea6_parse_options(config):
return options
def kea6_parse_subnet(subnet, config):
- out = {'subnet': subnet}
+ out = {'subnet': subnet, 'id': int(config['subnet_id'])}
options = kea6_parse_options(config)
if 'address_range' in config:
diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos
index ef8bf374a..0bb68b75d 100644
--- a/smoketest/config-tests/basic-vyos
+++ b/smoketest/config-tests/basic-vyos
@@ -24,12 +24,13 @@ set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50
set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40'
set protocols static route 0.0.0.0/0 next-hop 100.64.0.1
set service dhcp-server shared-network-name LAN authoritative
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.20'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1'
set service dns forwarding allow-from '192.168.0.0/16'
set service dns forwarding cache-size '10000'
set service dns forwarding dnssec 'off'
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
index 8f3e4ade3..bdae6e807 100644
--- a/smoketest/config-tests/dialup-router-medium-vpn
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -254,11 +254,11 @@ set service dhcp-server failover remote '192.168.0.251'
set service dhcp-server failover source-address '192.168.0.250'
set service dhcp-server failover status 'primary'
set service dhcp-server shared-network-name LAN authoritative
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 default-router '192.168.0.1'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-name 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 domain-search 'vyos.net'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-server '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.200'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio ip-address '192.168.0.107'
@@ -277,6 +277,7 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-map
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac '00:50:01:ba:62:79'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand ip-address '192.168.0.110'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping sand mac '00:50:01:af:c5:d2'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1'
set service dns forwarding allow-from '192.168.0.0/16'
set service dns forwarding cache-size '8192'
set service dns forwarding dnssec 'off'
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 6f24d40ec..3e6adb149 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -99,6 +99,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['listen-interface', interface])
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -122,6 +123,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'interfaces-config'], 'interfaces', [interface])
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
@@ -168,6 +170,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
ipv6_only_preferred = '300'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -294,6 +297,9 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
shared_network = base_path + ['shared-network-name', shared_net_name]
pool = shared_network + ['subnet', subnet]
+
+ self.cli_set(pool + ['subnet-id', '1'])
+
# we use the first subnet IP address as default gateway
self.cli_set(shared_network + ['option', 'default-router', router])
self.cli_set(shared_network + ['option', 'name-server', dns_1])
@@ -352,6 +358,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
domain_name = 'private'
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -390,6 +397,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
@@ -437,6 +445,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_1_stop = inc_ip(subnet, 40)
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', str(int(network) + 1)])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -474,6 +483,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'id', int(network) + 1)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'valid-lifetime', int(lease_time))
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', int(network), 'subnet4'], 'max-valid-lifetime', int(lease_time))
@@ -521,6 +531,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_0_stop = inc_ip(subnet, 20)
pool = base_path + ['shared-network-name', 'EXCLUDE-TEST', 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['exclude', router])
self.cli_set(pool + ['range', '0', 'start', range_0_start])
@@ -563,6 +574,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_0_start_excl = inc_ip(exclude_addr, 1)
pool = base_path + ['shared-network-name', 'EXCLUDE-TEST-2', 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['exclude', exclude_addr])
self.cli_set(pool + ['range', '0', 'start', range_0_start])
@@ -608,6 +620,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_0_stop = '10.0.250.255'
pool = base_path + ['shared-network-name', 'RELAY', 'subnet', relay_subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
self.cli_set(pool + ['option', 'default-router', relay_router])
self.cli_set(pool + ['range', '0', 'start', range_0_start])
self.cli_set(pool + ['range', '0', 'stop', range_0_stop])
@@ -645,6 +658,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
range_0_stop = inc_ip(subnet, 20)
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
+ self.cli_set(pool + ['subnet-id', '1'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index f163cc69a..fcbfeb7be 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -102,7 +102,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
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])
@@ -145,6 +145,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'valid-lifetime', int(lease_time))
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'min-valid-lifetime', int(min_lease_time))
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'max-valid-lifetime', int(max_lease_time))
@@ -215,7 +216,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
prefix_len = '56'
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 + ['prefix-delegation', 'prefix', delegate_start, 'delegated-length', delegate_len])
self.cli_set(pool + ['prefix-delegation', 'prefix', delegate_start, 'prefix-length', prefix_len])
@@ -250,7 +251,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_1])
self.cli_set(base_path + ['global-parameters', 'name-server', ns_global_2])
- self.cli_set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet])
+ self.cli_set(base_path + ['shared-network-name', shared_net_name, 'subnet', subnet, 'subnet-id', '1'])
# commit changes
self.cli_commit()
@@ -260,6 +261,7 @@ class TestServiceDHCPv6Server(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp6', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'subnet', subnet)
+ self.verify_config_value(obj, ['Dhcp6', 'shared-networks', 0, 'subnet6'], 'id', 1)
self.verify_config_object(
obj,
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index ceaba019e..2418c8faa 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -165,6 +165,7 @@ def verify(dhcp):
shared_networks = len(dhcp['shared_network_name'])
disabled_shared_networks = 0
+ subnet_ids = []
# A shared-network requires a subnet definition
for network, network_config in dhcp['shared_network_name'].items():
@@ -176,6 +177,14 @@ def verify(dhcp):
'lease subnet must be configured.')
for subnet, subnet_config in network_config['subnet'].items():
+ if 'subnet_id' not in subnet_config:
+ raise ConfigError(f'Unique subnet ID not specified for subnet "{subnet}"')
+
+ if subnet_config['subnet_id'] in subnet_ids:
+ raise ConfigError(f'Subnet ID for subnet "{subnet}" is not unique')
+
+ subnet_ids.append(subnet_config['subnet_id'])
+
# All delivered static routes require a next-hop to be set
if 'static_route' in subnet_config:
for route, route_option in subnet_config['static_route'].items():
diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py
index 9cc57dbcf..7cd801cdd 100755
--- a/src/conf_mode/service_dhcpv6-server.py
+++ b/src/conf_mode/service_dhcpv6-server.py
@@ -63,6 +63,7 @@ def verify(dhcpv6):
# Inspect shared-network/subnet
subnets = []
+ subnet_ids = []
listen_ok = False
for network, network_config in dhcpv6['shared_network_name'].items():
# A shared-network requires a subnet definition
@@ -72,6 +73,14 @@ def verify(dhcpv6):
'each shared network!')
for subnet, subnet_config in network_config['subnet'].items():
+ if 'subnet_id' not in subnet_config:
+ raise ConfigError(f'Unique subnet ID not specified for subnet "{subnet}"')
+
+ if subnet_config['subnet_id'] in subnet_ids:
+ raise ConfigError(f'Subnet ID for subnet "{subnet}" is not unique')
+
+ subnet_ids.append(subnet_config['subnet_id'])
+
if 'address_range' in subnet_config:
if 'start' in subnet_config['address_range']:
range6_start = []
diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9
index 908420c18..810e403a6 100755
--- a/src/migration-scripts/dhcp-server/8-to-9
+++ b/src/migration-scripts/dhcp-server/8-to-9
@@ -16,6 +16,7 @@
# T3316:
# - Migrate dhcp options under new option node
+# - Add subnet IDs to existing subnets
import sys
import re
@@ -44,6 +45,8 @@ option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-po
'tftp-server-name', 'time-offset', 'time-server', 'time-zone',
'vendor-option', 'wins-server', 'wpad-url']
+subnet_id = 1
+
for network in config.list_nodes(base):
for option in option_nodes:
if config.exists(base + [network, option]):
@@ -56,10 +59,13 @@ for network in config.list_nodes(base):
base_subnet = base + [network, 'subnet', subnet]
for option in option_nodes:
- if config.exists(base + [network, 'subnet', subnet, option]):
- config.set(base + [network, 'subnet', subnet, 'option'])
- config.copy(base + [network, 'subnet', subnet, option], base + [network, 'subnet', subnet, 'option', option])
- config.delete(base + [network, 'subnet', subnet, option])
+ 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
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dhcpv6-server/3-to-4 b/src/migration-scripts/dhcpv6-server/3-to-4
new file mode 100755
index 000000000..c065e3d43
--- /dev/null
+++ b/src/migration-scripts/dhcpv6-server/3-to-4
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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 .
+
+# T3316:
+# - Add subnet IDs to existing subnets
+
+import sys
+import re
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcpv6-server', 'shared-network-name']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+subnet_id = 1
+
+for network in config.list_nodes(base):
+ if config.exists(base + [network, 'subnet']):
+ for subnet in config.list_nodes(base + [network, 'subnet']):
+ base_subnet = base + [network, 'subnet', subnet]
+
+ config.set(base_subnet + ['subnet-id'], value=subnet_id)
+ subnet_id += 1
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
--
cgit v1.2.3