diff options
-rw-r--r-- | data/templates/accel-ppp/config_ip_pool.j2 | 10 | ||||
-rw-r--r-- | data/templates/accel-ppp/pppoe.config.tmpl | 15 | ||||
-rw-r--r-- | interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i | 18 | ||||
-rw-r--r-- | interface-definitions/service_ipoe-server.xml.in | 14 | ||||
-rw-r--r-- | interface-definitions/service_pppoe-server.xml.in | 1 | ||||
-rw-r--r-- | python/vyos/configverify.py | 16 | ||||
-rw-r--r-- | python/vyos/util.py | 22 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_service_pppoe-server.py | 29 | ||||
-rw-r--r-- | src/tests/test_dict_search.py | 25 |
9 files changed, 131 insertions, 19 deletions
diff --git a/data/templates/accel-ppp/config_ip_pool.j2 b/data/templates/accel-ppp/config_ip_pool.j2 index 3b0f68084..f61a4f148 100644 --- a/data/templates/accel-ppp/config_ip_pool.j2 +++ b/data/templates/accel-ppp/config_ip_pool.j2 @@ -11,4 +11,14 @@ gw-ip-address={{ gateway_address }} {{ subnet }} {% endfor %} {% endif %} +{% if client_ip_pool.name is defined and client_ip_pool.name is not none %} +{% for pool, pool_config in client_ip_pool.name.items() %} +{% if pool_config.subnet is defined and pool_config.subnet is not none %} +{{ pool_config.subnet }},name={{ pool }} +{% endif %} +{% if pool_config.gateway_address is defined and pool_config.gateway_address is not none %} +gw-ip-address={{ pool_config.gateway_address }} +{% endif %} +{% endfor %} +{% endif %} {% endif %} diff --git a/data/templates/accel-ppp/pppoe.config.tmpl b/data/templates/accel-ppp/pppoe.config.tmpl index 46f462166..b37004f5b 100644 --- a/data/templates/accel-ppp/pppoe.config.tmpl +++ b/data/templates/accel-ppp/pppoe.config.tmpl @@ -136,6 +136,21 @@ pado-delay={{ pado_delay_param.value }} called-sid={{ authentication.radius.called_sid_format }} {% endif %} +{% if authentication is defined and authentication.mode is defined and authentication.mode == 'local' %} +{% if client_ip_pool is defined and client_ip_pool is not none %} +{% if client_ip_pool.name is defined and client_ip_pool.name is not none %} +{% for pool, pool_config in client_ip_pool.name.items() %} +{% if pool_config.subnet is defined and pool_config.subnet is not none %} +ip-pool={{ pool }} +{% if pool_config.gateway_address is defined and pool_config.gateway_address is not none %} +gw-ip-address={{ pool_config.gateway_address }}/{{ pool_config.subnet.split('/')[1] }} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} +{% endif %} +{% endif %} + {% if limits is defined %} [connlimit] {% if limits.connection_limit is defined and limits.connection_limit is not none %} diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i new file mode 100644 index 000000000..654b6727e --- /dev/null +++ b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i @@ -0,0 +1,18 @@ +<!-- include start from accel-ppp/client-ip-pool-name.xml.i --> +<tagNode name="name"> + <properties> + <help>Pool name</help> + <valueHelp> + <format>txt</format> + <description>Name of IP pool</description> + </valueHelp> + <constraint> + <regex>[-_a-zA-Z0-9.]+</regex> + </constraint> + </properties> + <children> + #include <include/accel-ppp/gateway-address.xml.i> + #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i> + </children> +</tagNode> +<!-- include end --> diff --git a/interface-definitions/service_ipoe-server.xml.in b/interface-definitions/service_ipoe-server.xml.in index b9279793c..d81ec99f9 100644 --- a/interface-definitions/service_ipoe-server.xml.in +++ b/interface-definitions/service_ipoe-server.xml.in @@ -117,19 +117,7 @@ <help>Client IP pools and gateway setting</help> </properties> <children> - <tagNode name="name"> - <properties> - <help>Pool name</help> - <constraint> - <regex>[-_a-zA-Z0-9]+</regex> - </constraint> - <constraintErrorMessage>Client IP pool is limited to alphanumerical characters and can contain hyphen and underscores</constraintErrorMessage> - </properties> - <children> - #include <include/accel-ppp/gateway-address.xml.i> - #include <include/accel-ppp/client-ip-pool-subnet-single.xml.i> - </children> - </tagNode> + #include <include/accel-ppp/client-ip-pool-name.xml.i> </children> </node> #include <include/accel-ppp/client-ipv6-pool.xml.i> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 25ab6f9a0..65868226b 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -56,6 +56,7 @@ <children> #include <include/accel-ppp/client-ip-pool-start-stop.xml.i> #include <include/accel-ppp/client-ip-pool-subnet.xml.i> + #include <include/accel-ppp/client-ip-pool-name.xml.i> </children> </node> #include <include/accel-ppp/client-ipv6-pool.xml.i> diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index a35ea0b74..47cf218ee 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -1,4 +1,4 @@ -# Copyright 2020-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -23,6 +23,7 @@ from vyos import ConfigError from vyos.util import dict_search +from vyos.util import dict_search_recursive def verify_mtu(config): """ @@ -355,7 +356,17 @@ def verify_accel_ppp_base_service(config): if 'key' not in radius_config: raise ConfigError(f'Missing RADIUS secret key for server "{server}"') - if 'gateway_address' not in config: + # Check global gateway or gateway in named pool + gateway = False + if 'gateway_address' in config: + gateway = True + else: + if dict_search_recursive(config, 'gateway_address', ['client_ip_pool', 'name']): + for _, v in config['client_ip_pool']['name'].items(): + if 'gateway_address' in v: + gateway = True + break + if not gateway: raise ConfigError('Server requires gateway-address to be configured!') if 'name_server_ipv4' in config: @@ -427,4 +438,3 @@ def verify_common_route_maps(config): for protocol, protocol_config in config['redistribute'].items(): if 'route_map' in protocol_config: verify_route_map(protocol_config['route_map'], config) - diff --git a/python/vyos/util.py b/python/vyos/util.py index 5c6409341..32a5ae116 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1,4 +1,4 @@ -# Copyright 2020-2022 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2020-2023 VyOS maintainers and contributors <maintainers@vyos.io> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -720,6 +720,26 @@ def dict_search(path, dict_object): c = c.get(p, {}) return c.get(parts[-1], None) +def dict_search_recursive(dict_object, key, path=[]): + """ Traverse a dictionary recurisvely and return the value of the key + we are looking for. + Thankfully copied from https://stackoverflow.com/a/19871956 + Modified to yield optional path to found keys + """ + if isinstance(dict_object, list): + for i in dict_object: + new_path = path + [i] + for x in dict_search_recursive(i, key, new_path): + yield x + elif isinstance(dict_object, dict): + if key in dict_object: + new_path = path + [key] + yield dict_object[key], new_path + for k, j in dict_object.items(): + new_path = path + [k] + for x in dict_search_recursive(j, key, new_path): + yield x + def convert_data(data): """Convert multiple types of data to types usable in CLI diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py index e5acff265..8514801a8 100755 --- a/smoketest/scripts/cli/test_service_pppoe-server.py +++ b/smoketest/scripts/cli/test_service_pppoe-server.py @@ -175,6 +175,35 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase): self.assertTrue(process_named_running(self._process_name)) + def test_pppoe_server_client_ip_pool_name(self): + # Test configuration of named client pools + self.basic_config() + + subnet = '192.0.2.0/24' + gateway = '192.0.2.1' + pool = 'VYOS' + + subnet_name = f'{subnet},name' + gw_ip_prefix = f'{gateway}/24' + + self.set(['client-ip-pool', 'name', pool, 'subnet', subnet]) + self.set(['client-ip-pool', 'name', pool, 'gateway-address', gateway]) + self.cli_delete(self._base_path + ['gateway-address']) + + # commit changes + self.cli_commit() + + # Validate configuration values + conf = ConfigParser(allow_no_value=True, delimiters='=') + conf.read(self._config_file) + + # Validate configuration + self.assertEqual(conf['ip-pool'][subnet_name], pool) + self.assertEqual(conf['ip-pool']['gw-ip-address'], gateway) + self.assertEqual(conf['pppoe']['ip-pool'], pool) + self.assertEqual(conf['pppoe']['gw-ip-address'], gw_ip_prefix) + + def test_pppoe_server_client_ipv6_pool(self): # Test configuration of IPv6 client pools self.basic_config() diff --git a/src/tests/test_dict_search.py b/src/tests/test_dict_search.py index 6a0fc74ad..deee9a657 100644 --- a/src/tests/test_dict_search.py +++ b/src/tests/test_dict_search.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020 VyOS maintainers and contributors +# Copyright (C) 2020-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 @@ -16,14 +16,28 @@ from unittest import TestCase from vyos.util import dict_search +from vyos.util import dict_search_recursive data = { 'string': 'fooo', 'nested': {'string': 'bar', 'empty': '', 'list': ['foo', 'bar']}, + 'non': {}, 'list': ['bar', 'baz'], - 'dict': {'key_1': {}, 'key_2': 'vyos'} + 'dict': {'key_1': {}, 'key_2': 'vyos'}, + 'interfaces': {'dummy': {'dum0': {'address': ['192.0.2.17/29']}}, + 'ethernet': {'eth0': {'address': ['2001:db8::1/64', '192.0.2.1/29'], + 'description': 'Test123', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:01', + 'speed': 'auto'}, + 'eth1': {'address': ['192.0.2.9/29'], + 'description': 'Test456', + 'duplex': 'auto', + 'hw_id': '00:00:00:00:00:02', + 'speed': 'auto'}}} } + class TestDictSearch(TestCase): def setUp(self): pass @@ -55,3 +69,10 @@ class TestDictSearch(TestCase): def test_nested_list(self): # TestDictSearch: Return list items when querying nested list self.assertEqual(dict_search('nested.list', data), data['nested']['list']) + + def test_dict_search_recursive(self): + # Test nested search in dictionary + tmp = list(dict_search_recursive(data, 'hw_id')) + self.assertEqual(len(tmp), 2) + tmp = list(dict_search_recursive(data, 'address')) + self.assertEqual(len(tmp), 3) |