summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorViacheslav Hletenko <v.gletenko@vyos.io>2023-03-21 14:47:48 +0000
committerViacheslav Hletenko <v.gletenko@vyos.io>2023-03-21 14:47:48 +0000
commit7cb95a6bc9801abcc70f8d4cfbcc79718148de1c (patch)
treeaa59de5a534ad782cbf8c3ab4f34c2af280b956a
parentc820be57b6c77cdb0a5055d0c3a77dc8d550e2d7 (diff)
downloadvyos-1x-7cb95a6bc9801abcc70f8d4cfbcc79718148de1c.tar.gz
vyos-1x-7cb95a6bc9801abcc70f8d4cfbcc79718148de1c.zip
T5099: IPoE-server add option next-pool for named ip pools
In cases with multiple named IP pools, it is required the option 'next' to be sure that if IP addresses ended in one pool, then they would begin to be allocated from the next named pool. For accel-ppp it requires specific order as pool must be defined before we can use it with the 'next-option' set service ipoe-server client-ip-pool name first-pool subnet '192.0.2.0/25' set service ipoe-server client-ip-pool name first-pool next-pool 'second-pool' set service ipoe-server client-ip-pool name second-pool subnet '203.0.113.0/25' [ip-pool] 203.0.113.0/25,name=second-pool 192.0.2.0/25,name=first-pool,next=second-pool
-rw-r--r--data/templates/accel-ppp/ipoe.config.j227
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i12
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py93
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py97
4 files changed, 222 insertions, 7 deletions
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index ac83c3dbd..add3dc7e4 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -49,22 +49,35 @@ username=ifname
password=csid
{% endif %}
{% if client_ip_pool.name is vyos_defined %}
-{% for pool, pool_options in client_ip_pool.name.items() %}
-{% if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %}
+{% if first_named_pool is vyos_defined %}
+ip-pool={{ first_named_pool }}
+{% else %}
+{% for pool, pool_options in client_ip_pool.name.items() %}
+{% if pool_options.subnet is vyos_defined %}
ip-pool={{ pool }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% for pool, pool_options in client_ip_pool.name.items() %}
+{% if pool_options.gateway_address is vyos_defined %}
gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}
{% endif %}
{% endfor %}
{% endif %}
proxy-arp=1
-{% if client_ip_pool.name is vyos_defined %}
+{% if ordered_named_pools is vyos_defined %}
[ip-pool]
-{% for pool, pool_options in client_ip_pool.name.items() %}
-{% if pool_options.subnet is vyos_defined and pool_options.gateway_address is vyos_defined %}
-{{ pool_options.subnet }},name={{ pool }}
+{% for p in ordered_named_pools %}
+{% for pool, pool_options in p.items() %}
+{% set next_named_pool = ',next=' ~ pool_options.next_pool if pool_options.next_pool is vyos_defined else '' %}
+{{ pool_options.subnet }},name={{ pool }}{{ next_named_pool }}
+{% endfor %}
+{% endfor %}
+{% for p in ordered_named_pools %}
+{% for pool, pool_options in p.items() %}
gw-ip-address={{ pool_options.gateway_address }}/{{ pool_options.subnet.split('/')[1] }}
-{% endif %}
+{% endfor %}
{% endfor %}
{% endif %}
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
index 654b6727e..b442a15b9 100644
--- a/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i
+++ b/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i
@@ -13,6 +13,18 @@
<children>
#include <include/accel-ppp/gateway-address.xml.i>
#include <include/accel-ppp/client-ip-pool-subnet-single.xml.i>
+ <leafNode name="next-pool">
+ <properties>
+ <help>Next pool name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of IP pool</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_a-zA-Z0-9.]+</regex>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<!-- include end -->
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index bdab35834..8a141b8f0 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -26,6 +26,13 @@ from configparser import ConfigParser
ac_name = 'ACN'
interface = 'eth0'
+
+def getConfig(string, end='cli'):
+ command = f'cat /run/accel-pppd/ipoe.conf | sed -n "/^{string}/,/^{end}/p"'
+ out = cmd(command)
+ return out
+
+
class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -86,6 +93,92 @@ class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
tmp = re.findall(regex, tmp)
self.assertTrue(tmp)
+ def test_accel_named_pool(self):
+ first_pool = 'VyOS-pool1'
+ first_subnet = '192.0.2.0/25'
+ first_gateway = '192.0.2.1'
+ second_pool = 'Vyos-pool2'
+ second_subnet = '203.0.113.0/25'
+ second_gateway = '203.0.113.1'
+
+ self.set(['authentication', 'mode', 'noauth'])
+ self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway])
+ self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet])
+ self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway])
+ self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet])
+ self.set(['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+
+ self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1')
+ self.assertTrue(conf['ipoe']['noauth'], '1')
+ self.assertTrue(conf['ipoe']['ip-pool'], first_pool)
+ self.assertTrue(conf['ipoe']['ip-pool'], second_pool)
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25')
+
+ config = getConfig('[ip-pool]')
+ pool_config = f'''{second_subnet},name={second_pool}
+{first_subnet},name={first_pool}
+gw-ip-address={second_gateway}/25
+gw-ip-address={first_gateway}/25'''
+ self.assertIn(pool_config, config)
+
+
+ def test_accel_next_pool(self):
+ first_pool = 'VyOS-pool1'
+ first_subnet = '192.0.2.0/25'
+ first_gateway = '192.0.2.1'
+ second_pool = 'Vyos-pool2'
+ second_subnet = '203.0.113.0/25'
+ second_gateway = '203.0.113.1'
+ third_pool = 'Vyos-pool3'
+ third_subnet = '198.51.100.0/24'
+ third_gateway = '198.51.100.1'
+
+ self.set(['authentication', 'mode', 'noauth'])
+ self.set(['client-ip-pool', 'name', first_pool, 'gateway-address', first_gateway])
+ self.set(['client-ip-pool', 'name', first_pool, 'subnet', first_subnet])
+ self.set(['client-ip-pool', 'name', first_pool, 'next-pool', second_pool])
+ self.set(['client-ip-pool', 'name', second_pool, 'gateway-address', second_gateway])
+ self.set(['client-ip-pool', 'name', second_pool, 'subnet', second_subnet])
+ self.set(['client-ip-pool', 'name', second_pool, 'next-pool', third_pool])
+ self.set(['client-ip-pool', 'name', third_pool, 'gateway-address', third_gateway])
+ self.set(['client-ip-pool', 'name', third_pool, 'subnet', third_subnet])
+ self.set(['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf.read(self._config_file)
+
+ self.assertTrue(conf['ipoe']['interface'], f'{interface},shared=1,mode=L2,ifcfg=1,start=dhcpv4,ipv6=1')
+ self.assertTrue(conf['ipoe']['noauth'], '1')
+ self.assertTrue(conf['ipoe']['ip-pool'], first_pool)
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{first_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{second_gateway}/25')
+ self.assertTrue(conf['ipoe']['gw-ip-address'], f'{third_gateway}/24')
+
+ config = getConfig('[ip-pool]')
+ # T5099 required specific order
+ pool_config = f'''{third_subnet},name={third_pool}
+{second_subnet},name={second_pool},next={third_pool}
+{first_subnet},name={first_pool},next={second_pool}
+gw-ip-address={third_gateway}/24
+gw-ip-address={second_gateway}/25
+gw-ip-address={first_gateway}/25'''
+ self.assertIn(pool_config, config)
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/service_ipoe-server.py b/src/conf_mode/service_ipoe-server.py
index 4fabe170f..95c72df47 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
+import jmespath
from sys import exit
@@ -29,9 +30,92 @@ from vyos import ConfigError
from vyos import airbag
airbag.enable()
+
ipoe_conf = '/run/accel-pppd/ipoe.conf'
ipoe_chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
+
+def get_pools_in_order(data: dict) -> list:
+ """Return a list of dictionaries representing pool data in the order
+ in which they should be allocated. Pool must be defined before we can
+ use it with 'next-pool' option.
+
+ Args:
+ data: A dictionary of pool data, where the keys are pool names and the
+ values are dictionaries containing the 'subnet' key and the optional
+ 'next_pool' key.
+
+ Returns:
+ list: A list of dictionaries
+
+ Raises:
+ ValueError: If a 'next_pool' key references a pool name that
+ has not been defined.
+ ValueError: If a circular reference is found in the 'next_pool' keys.
+
+ Example:
+ config_data = {
+ ... 'first-pool': {
+ ... 'next_pool': 'second-pool',
+ ... 'subnet': '192.0.2.0/25'
+ ... },
+ ... 'second-pool': {
+ ... 'next_pool': 'third-pool',
+ ... 'subnet': '203.0.113.0/25'
+ ... },
+ ... 'third-pool': {
+ ... 'subnet': '198.51.100.0/24'
+ ... },
+ ... 'foo': {
+ ... 'subnet': '100.64.0.0/24',
+ ... 'next_pool': 'second-pool'
+ ... }
+ ... }
+
+ % get_pools_in_order(config_data)
+ [{'third-pool': {'subnet': '198.51.100.0/24'}},
+ {'second-pool': {'next_pool': 'third-pool', 'subnet': '203.0.113.0/25'}},
+ {'first-pool': {'next_pool': 'second-pool', 'subnet': '192.0.2.0/25'}},
+ {'foo': {'next_pool': 'second-pool', 'subnet': '100.64.0.0/24'}}]
+ """
+ pools = []
+ unresolved_pools = {}
+
+ for pool, pool_config in data.items():
+ if 'next_pool' not in pool_config:
+ pools.insert(0, {pool: pool_config})
+ else:
+ unresolved_pools[pool] = pool_config
+
+ while unresolved_pools:
+ resolved_pools = []
+
+ for pool, pool_config in unresolved_pools.items():
+ next_pool_name = pool_config['next_pool']
+
+ if any(p for p in pools if next_pool_name in p):
+ index = next(
+ (i for i, p in enumerate(pools) if next_pool_name in p),
+ None)
+ pools.insert(index + 1, {pool: pool_config})
+ resolved_pools.append(pool)
+ elif next_pool_name in unresolved_pools:
+ # next pool not yet resolved
+ pass
+ else:
+ raise ValueError(
+ f"Pool '{next_pool_name}' not defined in configuration data"
+ )
+
+ if not resolved_pools:
+ raise ValueError("Circular reference in configuration data")
+
+ for pool in resolved_pools:
+ unresolved_pools.pop(pool)
+
+ return pools
+
+
def get_config(config=None):
if config:
conf = config
@@ -43,6 +127,19 @@ def get_config(config=None):
# retrieve common dictionary keys
ipoe = get_accel_dict(conf, base, ipoe_chap_secrets)
+
+ if jmespath.search('client_ip_pool.name', ipoe):
+ dict_named_pools = jmespath.search('client_ip_pool.name', ipoe)
+ # Multiple named pools require ordered values T5099
+ ipoe['ordered_named_pools'] = get_pools_in_order(dict_named_pools)
+ # T5099 'next-pool' option
+ if jmespath.search('client_ip_pool.name.*.next_pool', ipoe):
+ for pool, pool_config in ipoe['client_ip_pool']['name'].items():
+ if 'next_pool' in pool_config:
+ ipoe['first_named_pool'] = pool
+ ipoe['first_named_pool_subnet'] = pool_config
+ break
+
return ipoe