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