diff options
-rw-r--r-- | interface-definitions/dns-dynamic.xml.in | 1 | ||||
-rw-r--r-- | smoketest/config-tests/dialup-router-medium-vpn | 16 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_container.py | 112 | ||||
-rwxr-xr-x | src/conf_mode/nat.py | 23 | ||||
-rwxr-xr-x | src/conf_mode/nat66.py | 8 |
5 files changed, 118 insertions, 42 deletions
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in index 388e7c5d2..d296a6694 100644 --- a/interface-definitions/dns-dynamic.xml.in +++ b/interface-definitions/dns-dynamic.xml.in @@ -10,6 +10,7 @@ <node name="dynamic" owner="${vyos_conf_scripts_dir}/dns_dynamic.py"> <properties> <help>Dynamic DNS</help> + <priority>990</priority> </properties> <children> <tagNode name="name"> diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn index 039a50594..8f3e4ade3 100644 --- a/smoketest/config-tests/dialup-router-medium-vpn +++ b/smoketest/config-tests/dialup-router-medium-vpn @@ -262,21 +262,21 @@ set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 name-serve 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' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac-address '00:50:01:dc:91:14' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Audio mac '00:50:01:dc:91:14' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV ip-address '192.168.0.104' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac-address '00:50:01:31:b5:f6' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping IPTV mac '00:50:01:31:b5:f6' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus ip-address '192.168.0.60' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac-address '00:50:01:58:ac:95' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping McPrintus mac '00:50:01:58:ac:95' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 ip-address '192.168.0.109' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac-address '00:50:01:bc:ac:51' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping Mobile01 mac '00:50:01:bc:ac:51' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 ip-address '192.168.0.11' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac-address '00:50:01:70:b9:4d' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera1 mac '00:50:01:70:b9:4d' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 ip-address '192.168.0.12' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac-address '00:50:01:70:b7:4f' +set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping camera2 mac '00:50:01:70:b7:4f' set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV ip-address '192.168.0.101' -set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping pearTV mac-address '00:50:01:ba:62:79' +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-address '00:50:01:af:c5:d2' +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 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_container.py b/smoketest/scripts/cli/test_container.py index b43c05fae..cdf46a6e1 100755 --- a/smoketest/scripts/cli/test_container.py +++ b/smoketest/scripts/cli/test_container.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 VyOS maintainers and contributors +# Copyright (C) 2021-2023 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 @@ -19,6 +19,7 @@ import glob import json from base_vyostest_shim import VyOSUnitTestSHIM +from ipaddress import ip_interface from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd @@ -27,8 +28,6 @@ from vyos.utils.file import read_file base_path = ['container'] cont_image = 'busybox:stable' # busybox is included in vyos-build -prefix = '192.168.205.0/24' -net_name = 'NET01' PROCESS_NAME = 'conmon' PROCESS_PIDFILE = '/run/vyos-container-{0}.service.pid' @@ -37,10 +36,8 @@ busybox_image_path = '/usr/share/vyos/busybox-stable.tar' def cmd_to_json(command): c = cmd(command + ' --format=json') data = json.loads(c)[0] - return data - class TestContainer(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -52,6 +49,10 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): except: cls.skipTest(cls, reason='busybox image not available') + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + @classmethod def tearDownClass(cls): super(TestContainer, cls).tearDownClass() @@ -70,7 +71,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): units = glob.glob('/run/systemd/system/vyos-container-*') self.assertEqual(units, []) - def test_01_basic_container(self): + def test_basic(self): cont_name = 'c1' self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', '10.0.2.15/24']) @@ -91,24 +92,101 @@ class TestContainer(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertEqual(process_named_running(PROCESS_NAME), pid) - def test_02_container_network(self): - cont_name = 'c2' - cont_ip = '192.168.205.25' + def test_ipv4_network(self): + prefix = '192.0.2.0/24' + base_name = 'ipv4' + net_name = 'NET01' + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) - self.cli_set(base_path + ['name', cont_name, 'image', cont_image]) - self.cli_set(base_path + ['name', cont_name, 'network', net_name, 'address', cont_ip]) - # commit changes + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) self.cli_commit() n = cmd_to_json(f'sudo podman network inspect {net_name}') - json_subnet = n['subnets'][0]['subnet'] + self.assertEqual(n['subnets'][0]['subnet'], prefix) - c = cmd_to_json(f'sudo podman container inspect {cont_name}') - json_ip = c['NetworkSettings']['Networks'][net_name]['IPAddress'] + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'], str(ip_interface(prefix).ip + ii)) - self.assertEqual(json_subnet, prefix) - self.assertEqual(json_ip, cont_ip) + def test_ipv6_network(self): + prefix = '2001:db8::/64' + base_name = 'ipv6' + net_name = 'NET02' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix).ip + ii)) + + def test_dual_stack_network(self): + prefix4 = '192.0.2.0/24' + prefix6 = '2001:db8::/64' + base_name = 'dual-stack' + net_name = 'net-4-6' + + self.cli_set(base_path + ['network', net_name, 'prefix', prefix4]) + self.cli_set(base_path + ['network', net_name, 'prefix', prefix6]) + + for ii in range(1, 6): + name = f'{base_name}-{ii}' + self.cli_set(base_path + ['name', name, 'image', cont_image]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix4).ip + ii)]) + self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix6).ip + ii)]) + + # verify() - first IP address of a prefix can not be used by a container + with self.assertRaises(ConfigSessionError): + self.cli_commit() + + tmp = f'{base_name}-1' + self.cli_delete(base_path + ['name', tmp]) + self.cli_commit() + + n = cmd_to_json(f'sudo podman network inspect {net_name}') + self.assertEqual(n['subnets'][0]['subnet'], prefix4) + self.assertEqual(n['subnets'][1]['subnet'], prefix6) + + # skipt first container, it was never created + for ii in range(2, 6): + name = f'{base_name}-{ii}' + c = cmd_to_json(f'sudo podman container inspect {name}') + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPv6Gateway'] , str(ip_interface(prefix6).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['GlobalIPv6Address'], str(ip_interface(prefix6).ip + ii)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1)) + self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py index 44b13d413..20570da62 100755 --- a/src/conf_mode/nat.py +++ b/src/conf_mode/nat.py @@ -80,15 +80,13 @@ def verify_rule(config, err_msg, groups_dict): dict_search('source.port', config)): if config['protocol'] not in ['tcp', 'udp', 'tcp_udp']: - raise ConfigError(f'{err_msg}\n' \ - 'ports can only be specified when protocol is '\ - 'either tcp, udp or tcp_udp!') + raise ConfigError(f'{err_msg} ports can only be specified when '\ + 'protocol is either tcp, udp or tcp_udp!') if is_ip_network(dict_search('translation.address', config)): - raise ConfigError(f'{err_msg}\n' \ - 'Cannot use ports with an IPv4 network as translation address as it\n' \ - 'statically maps a whole network of addresses onto another\n' \ - 'network of addresses') + raise ConfigError(f'{err_msg} cannot use ports with an IPv4 network as '\ + 'translation address as it statically maps a whole network '\ + 'of addresses onto another network of addresses!') for side in ['destination', 'source']: if side in config: @@ -152,10 +150,10 @@ def verify(nat): if 'outbound_interface' in config: if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: - raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"') + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"') elif 'name' in config['outbound_interface']: if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces(): - Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system') + Warning(f'NAT interface "{config["outbound_interface"]["name"]}" for source NAT rule "{rule}" does not exist!') if not dict_search('translation.address', config) and not dict_search('translation.port', config): if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -176,10 +174,10 @@ def verify(nat): if 'inbound_interface' in config: if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: - raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"') + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"') elif 'name' in config['inbound_interface']: if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces(): - Warning(f'{err_msg} - interface "{config["inbound_interface"]["name"]}" does not exist on this system') + Warning(f'NAT interface "{config["inbound_interface"]["name"]}" for destination NAT rule "{rule}" does not exist!') if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']: if 'exclude' not in config and 'backend' not in config['load_balance']: @@ -193,8 +191,7 @@ def verify(nat): err_msg = f'Static NAT configuration error in rule {rule}:' if 'inbound_interface' not in config: - raise ConfigError(f'{err_msg}\n' \ - 'inbound-interface not specified') + raise ConfigError(f'{err_msg} inbound-interface not specified') # common rule verification verify_rule(config, err_msg, nat['firewall_group']) diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py index dee1551fe..4c1ead258 100755 --- a/src/conf_mode/nat66.py +++ b/src/conf_mode/nat66.py @@ -64,10 +64,10 @@ def verify(nat): if 'outbound_interface' in config: if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']: - raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for nat source rule "{rule}"') + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"') elif 'name' in config['outbound_interface']: if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces(): - Warning(f'{err_msg} - interface "{config["outbound_interface"]["name"]}" does not exist on this system') + Warning(f'NAT66 interface "{config["outbound_interface"]["name"]}" for source NAT66 rule "{rule}" does not exist!') addr = dict_search('translation.address', config) if addr != None: @@ -88,10 +88,10 @@ def verify(nat): if 'inbound_interface' in config: if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']: - raise ConfigError(f'{err_msg} - Cannot specify both interface group and interface name for destination nat rule "{rule}"') + raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"') elif 'name' in config['inbound_interface']: if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces(): - Warning(f'{err_msg} - interface "{config["inbound_interface"]["name"]}" does not exist on this system') + Warning(f'NAT66 interface "{config["inbound_interface"]["name"]}" for destination NAT66 rule "{rule}" does not exist!') return None |