summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraapostoliuk <a.apostoliuk@vyos.io>2023-11-13 11:17:23 +0200
committerMergify <37929162+mergify[bot]@users.noreply.github.com>2023-12-28 15:28:02 +0000
commitd5062cb045fae8b0b5d68b3b1198c3b86de4d558 (patch)
tree589f2974f7589b7c9f12fb3388ac59e2efb14759
parentdb108da1fb9f289968302a963a0e6a28ea243b49 (diff)
downloadvyos-1x-d5062cb045fae8b0b5d68b3b1198c3b86de4d558.tar.gz
vyos-1x-d5062cb045fae8b0b5d68b3b1198c3b86de4d558.zip
accel-ppp: T5688: Standardized pool configuration in accel-ppp
Standardized pool configuration for all accel-ppp services. 1. Only named pools are used now. 2. Allows all services to use range in x.x.x.x/mask and x.x.x.x-x.x.x.y format 3. next-pool can be used in all services 2. Allows to use in ipoe gw-ip-address without pool configuration which allows to use Fraimed-IP-Address attribute by radius. 3. Default pool name should be explicidly configured with default-pool. 4. In ipoe netmask and range subnet can be different. (cherry picked from commit 422eb463d413da812eabc28706e507a9910d7b53)
-rw-r--r--data/templates/accel-ppp/config_chap_secrets_radius.j211
-rw-r--r--data/templates/accel-ppp/config_ip_pool.j232
-rw-r--r--data/templates/accel-ppp/ipoe.config.j236
-rw-r--r--data/templates/accel-ppp/l2tp.config.j223
-rw-r--r--data/templates/accel-ppp/pppoe.config.j221
-rw-r--r--data/templates/accel-ppp/pptp.config.j216
-rw-r--r--data/templates/accel-ppp/sstp.config.j24
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i30
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-start-stop.xml.i18
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i16
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool-subnet.xml.i16
-rw-r--r--interface-definitions/include/accel-ppp/client-ip-pool.xml.i46
-rw-r--r--interface-definitions/include/accel-ppp/default-pool.xml.i14
-rw-r--r--interface-definitions/include/accel-ppp/gateway-address-multi.xml.i17
-rw-r--r--interface-definitions/include/version/ipoe-server-version.xml.i2
-rw-r--r--interface-definitions/include/version/l2tp-version.xml.i2
-rw-r--r--interface-definitions/include/version/pppoe-server-version.xml.i2
-rw-r--r--interface-definitions/include/version/pptp-version.xml.i2
-rw-r--r--interface-definitions/include/version/sstp-version.xml.i2
-rw-r--r--interface-definitions/service-ipoe-server.xml.in11
-rw-r--r--interface-definitions/service-pppoe-server.xml.in12
-rw-r--r--interface-definitions/vpn-l2tp.xml.in11
-rw-r--r--interface-definitions/vpn-pptp.xml.in12
-rw-r--r--interface-definitions/vpn-sstp.xml.in10
-rw-r--r--python/vyos/accel_ppp_util.py193
-rw-r--r--python/vyos/configverify.py66
-rw-r--r--smoketest/scripts/cli/base_accel_ppp_test.py355
-rwxr-xr-xsmoketest/scripts/cli/test_service_ipoe-server.py210
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py65
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_l2tp.py212
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_pptp.py223
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_sstp.py13
-rwxr-xr-xsrc/conf_mode/service_ipoe-server.py102
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py18
-rwxr-xr-xsrc/conf_mode/vpn_l2tp.py47
-rwxr-xr-xsrc/conf_mode/vpn_pptp.py39
-rwxr-xr-xsrc/conf_mode/vpn_sstp.py13
-rwxr-xr-xsrc/migration-scripts/ipoe-server/1-to-287
-rwxr-xr-xsrc/migration-scripts/l2tp/4-to-577
-rwxr-xr-xsrc/migration-scripts/pppoe-server/6-to-7109
-rwxr-xr-xsrc/migration-scripts/pptp/2-to-364
-rwxr-xr-xsrc/migration-scripts/sstp/4-to-560
-rwxr-xr-xsrc/validators/ipv4-range-mask59
43 files changed, 1706 insertions, 672 deletions
diff --git a/data/templates/accel-ppp/config_chap_secrets_radius.j2 b/data/templates/accel-ppp/config_chap_secrets_radius.j2
index a498d8186..595e3a565 100644
--- a/data/templates/accel-ppp/config_chap_secrets_radius.j2
+++ b/data/templates/accel-ppp/config_chap_secrets_radius.j2
@@ -30,7 +30,16 @@ dae-server={{ authentication.radius.dynamic_author.server }}:{{ authentication.r
{% endif %}
{% endif %}
{# Both chap-secrets and radius block required the gw-ip-address #}
-{% if gateway_address is vyos_defined %}
+{% if authentication.mode is vyos_defined('local') or authentication.mode is vyos_defined('radius') %}
+{% if gateway_address is vyos_defined %}
+{% if server_type == 'ipoe' %}
+{% for gw in gateway_address %}
+{% set host_address, _ = gw.split('/') %}
+gw-ip-address={{ host_address }}
+{% endfor %}
+{% else %}
gw-ip-address={{ gateway_address }}
+{% endif %}
+{% endif %}
{% endif %}
diff --git a/data/templates/accel-ppp/config_ip_pool.j2 b/data/templates/accel-ppp/config_ip_pool.j2
index f7511e445..c567236a4 100644
--- a/data/templates/accel-ppp/config_ip_pool.j2
+++ b/data/templates/accel-ppp/config_ip_pool.j2
@@ -1,24 +1,22 @@
-{% if client_ip_pool is vyos_defined %}
+{% if ordered_named_pools is vyos_defined %}
[ip-pool]
{% if gateway_address is vyos_defined %}
+{% if server_type == 'ipoe' %}
+{% for gw in gateway_address %}
+{% set host_address, _ = gw.split('/') %}
+gw-ip-address={{ host_address }}
+{% endfor %}
+{% else %}
gw-ip-address={{ gateway_address }}
+{% endif %}
{% endif %}
-{% if client_ip_pool.start is vyos_defined and client_ip_pool.stop is vyos_defined %}
-{{ client_ip_pool.start }}-{{ client_ip_pool.stop.split('.')[3] }}
-{% endif %}
-{% if client_ip_pool.subnet is vyos_defined %}
-{% for subnet in client_ip_pool.subnet %}
-{{ subnet }}
-{% endfor %}
-{% endif %}
-{% if client_ip_pool.name is vyos_defined %}
-{% for pool, pool_config in client_ip_pool.name.items() %}
-{% if pool_config.subnet is vyos_defined %}
-{{ pool_config.subnet }},name={{ pool }}
-{% endif %}
-{% if pool_config.gateway_address is vyos_defined %}
-gw-ip-address={{ pool_config.gateway_address }}
+{% for pool in ordered_named_pools %}
+{% for pool_name, pool_config in pool.items() %}
+{% if pool_config.next_pool is vyos_defined %}
+{{ pool_config.range }},name={{ pool_name }},next={{ pool_config.next_pool }}
+{% else %}
+{{ pool_config.range }},name={{ pool_name }}
{% endif %}
{% endfor %}
-{% endif %}
+{% endfor %}
{% endif %} \ No newline at end of file
diff --git a/data/templates/accel-ppp/ipoe.config.j2 b/data/templates/accel-ppp/ipoe.config.j2
index 555a033d3..588f3d462 100644
--- a/data/templates/accel-ppp/ipoe.config.j2
+++ b/data/templates/accel-ppp/ipoe.config.j2
@@ -55,38 +55,18 @@ noauth=1
username=ifname
password=csid
{% endif %}
-{% if client_ip_pool.name 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 %}
+{% if default_pool is vyos_defined %}
+ip-pool={{ default_pool }}
+{% endif %}
+{% if gateway_address is vyos_defined %}
+{% for gw_addr in gateway_address %}
+gw-ip-address={{ gw_addr }}
{% endfor %}
{% endif %}
proxy-arp=1
-{% if ordered_named_pools is vyos_defined %}
-[ip-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] }}
-{% endfor %}
-{% endfor %}
-{% endif %}
+{# Common IP pool definitions #}
+{% include 'accel-ppp/config_ip_pool.j2' %}
{# Common IPv6 pool definitions #}
{% include 'accel-ppp/config_ipv6_pool.j2' %}
diff --git a/data/templates/accel-ppp/l2tp.config.j2 b/data/templates/accel-ppp/l2tp.config.j2
index b089d3e71..89cc0eae7 100644
--- a/data/templates/accel-ppp/l2tp.config.j2
+++ b/data/templates/accel-ppp/l2tp.config.j2
@@ -6,11 +6,9 @@ chap-secrets
{% for proto in auth_proto %}
{{ proto }}
{% endfor %}
-
{% if auth_mode == 'radius' %}
radius
{% endif %}
-
ippool
shaper
ipv6pool
@@ -65,24 +63,15 @@ secret={{ lns_shared_secret }}
{% if lns_host_name %}
host-name={{ lns_host_name }}
{% endif %}
+{% if default_pool is vyos_defined %}
+ip-pool={{ default_pool }}
+{% endif %}
[client-ip-range]
0.0.0.0/0
-{% if client_ip_pool or client_ip_subnets %}
-[ip-pool]
-{% if client_ip_pool %}
-{{ client_ip_pool }}
-{% endif %}
-{% if client_ip_subnets %}
-{% for sn in client_ip_subnets %}
-{{ sn }}
-{% endfor %}
-{% endif %}
-{% endif %}
-{% if gateway_address %}
-gw-ip-address={{ gateway_address }}
-{% endif %}
+{# Common IP pool definitions #}
+{% include 'accel-ppp/config_ip_pool.j2' %}
{% if auth_mode == 'local' %}
[chap-secrets]
@@ -115,7 +104,7 @@ nas-ip-address={{ radius_nas_ip }}
bind={{ radius_source_address }}
{% endif %}
{% endif %}
-{% if gateway_address %}
+{% if gateway_address is vyos_defined %}
gw-ip-address={{ gateway_address }}
{% endif %}
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index e1ae3660e..4bb1c4450 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -108,7 +108,6 @@ unit-cache={{ ppp_options.interface_cache }}
[pppoe]
verbose=1
ac-name={{ access_concentrator }}
-
{% if interface is vyos_defined %}
{% for iface, iface_config in interface.items() %}
{% if iface_config.vlan is not vyos_defined %}
@@ -121,11 +120,9 @@ vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% endif %}
{% endfor %}
{% endif %}
-
{% if service_name %}
service-name={{ service_name | join(',') }}
{% endif %}
-
{% if pado_delay %}
{% set pado_delay_param = namespace(value='0') %}
{% for delay in pado_delay | sort(attribute='0') %}
@@ -140,21 +137,11 @@ pado-delay={{ pado_delay_param.value }}
{% if authentication.radius.called_sid_format is vyos_defined %}
called-sid={{ authentication.radius.called_sid_format }}
{% endif %}
-
-{% if authentication.mode is vyos_defined("local") or authentication.mode is vyos_defined("noauth") %}
-{% if authentication.mode is vyos_defined("noauth") %}
+{% if authentication.mode is vyos_defined("noauth") %}
noauth=1
-{% endif %}
-{% if client_ip_pool.name is vyos_defined %}
-{% for pool, pool_config in client_ip_pool.name.items() %}
-{% if pool_config.subnet is vyos_defined %}
-ip-pool={{ pool }}
-{% endif %}
-{% if pool_config.gateway_address is vyos_defined %}
-gw-ip-address={{ pool_config.gateway_address }}/{{ pool_config.subnet.split('/')[1] }}
-{% endif %}
-{% endfor %}
-{% endif %}
+{% endif %}
+{% if default_pool is vyos_defined %}
+ip-pool={{ default_pool }}
{% endif %}
{% if limits is vyos_defined %}
diff --git a/data/templates/accel-ppp/pptp.config.j2 b/data/templates/accel-ppp/pptp.config.j2
index 46a9f933a..4e891777f 100644
--- a/data/templates/accel-ppp/pptp.config.j2
+++ b/data/templates/accel-ppp/pptp.config.j2
@@ -51,14 +51,15 @@ ppp-max-mtu={{ mtu }}
mppe={{ ppp_mppe }}
echo-interval=10
echo-failure=3
-
+{% if default_pool is vyos_defined %}
+ip-pool={{ default_pool }}
+{% endif %}
[client-ip-range]
0.0.0.0/0
-[ip-pool]
-tunnel={{ client_ip_pool }}
-gw-ip-address={{ gw_ip }}
+{# Common IP pool definitions #}
+{% include 'accel-ppp/config_ip_pool.j2' %}
[ppp]
verbose=5
@@ -74,18 +75,15 @@ verbose=1
{% for r in radius_server %}
server={{ r.server }},{{ r.key }},auth-port={{ r.port }},acct-port={{ r.acct_port }},req-limit=0,fail-time={{ r.fail_time }}
{% endfor %}
-
{% if radius_acct_interim_interval is vyos_defined %}
acct-interim-interval={{ radius_acct_interim_interval }}
{% endif %}
{% if radius_acct_inter_jitter %}
acct-interim-jitter={{ radius_acct_inter_jitter }}
{% endif %}
-
acct-timeout={{ radius_acct_tmo }}
timeout={{ radius_timeout }}
max-try={{ radius_max_try }}
-
{% if radius_nas_id %}
nas-identifier={{ radius_nas_id }}
{% endif %}
@@ -97,8 +95,8 @@ bind={{ radius_source_address }}
{% endif %}
{% endif %}
{# Both chap-secrets and radius block required the gw-ip-address #}
-{% if gw_ip is defined and gw_ip is not none %}
-gw-ip-address={{ gw_ip }}
+{% if gateway_address is vyos_defined %}
+gw-ip-address={{ gateway_address }}
{% endif %}
{% if radius_shaper_enable %}
diff --git a/data/templates/accel-ppp/sstp.config.j2 b/data/templates/accel-ppp/sstp.config.j2
index cf1d23f54..6117cea1b 100644
--- a/data/templates/accel-ppp/sstp.config.j2
+++ b/data/templates/accel-ppp/sstp.config.j2
@@ -36,6 +36,9 @@ accept=ssl
ssl-ca-file=/run/accel-pppd/sstp-ca.pem
ssl-pemfile=/run/accel-pppd/sstp-cert.pem
ssl-keyfile=/run/accel-pppd/sstp-cert.key
+{% if default_pool is vyos_defined %}
+ip-pool={{ default_pool }}
+{% endif %}
{# Common IP pool definitions #}
{% include 'accel-ppp/config_ip_pool.j2' %}
@@ -56,7 +59,6 @@ check-ip=1
mtu={{ mtu }}
ipv6={{ 'allow' if ppp_options.ipv6 is vyos_defined("deny") and client_ipv6_pool is vyos_defined else ppp_options.ipv6 }}
ipv4={{ ppp_options.ipv4 }}
-
mppe={{ ppp_options.mppe }}
lcp-echo-interval={{ ppp_options.lcp_echo_interval }}
lcp-echo-timeout={{ ppp_options.lcp_echo_timeout }}
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
deleted file mode 100644
index b442a15b9..000000000
--- a/interface-definitions/include/accel-ppp/client-ip-pool-name.xml.i
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- 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>
- <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/interface-definitions/include/accel-ppp/client-ip-pool-start-stop.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-start-stop.xml.i
deleted file mode 100644
index 5f4132d13..000000000
--- a/interface-definitions/include/accel-ppp/client-ip-pool-start-stop.xml.i
+++ /dev/null
@@ -1,18 +0,0 @@
-<!-- include start from accel-ppp/client-ip-pool-start-stop.xml.i -->
-<leafNode name="start">
- <properties>
- <help>First IP address in the pool</help>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
-</leafNode>
-<leafNode name="stop">
- <properties>
- <help>Last IP address in the pool</help>
- <constraint>
- <validator name="ipv4-address"/>
- </constraint>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i
deleted file mode 100644
index b93ba67d8..000000000
--- a/interface-definitions/include/accel-ppp/client-ip-pool-subnet-single.xml.i
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- include start from accel-ppp/client-ip-pool-subnet-single.xml.i -->
-<leafNode name="subnet">
- <properties>
- <help>Client IP subnet (CIDR notation)</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- <validator name="ipv4-host"/>
- </constraint>
- <constraintErrorMessage>Not a valid IP address or prefix</constraintErrorMessage>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/accel-ppp/client-ip-pool-subnet.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool-subnet.xml.i
deleted file mode 100644
index 2dc71d3f9..000000000
--- a/interface-definitions/include/accel-ppp/client-ip-pool-subnet.xml.i
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- include start from accel-ppp/client-ip-pool-subnet.xml.i -->
-<leafNode name="subnet">
- <properties>
- <help>Client IP subnet (CIDR notation)</help>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 address and prefix length</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-prefix"/>
- </constraint>
- <constraintErrorMessage>Not a valid CIDR formatted prefix</constraintErrorMessage>
- <multi />
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/accel-ppp/client-ip-pool.xml.i b/interface-definitions/include/accel-ppp/client-ip-pool.xml.i
new file mode 100644
index 000000000..dff574e6c
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/client-ip-pool.xml.i
@@ -0,0 +1,46 @@
+<!-- include start from accel-ppp/client-ip-pool.xml.i -->
+<tagNode name="client-ip-pool">
+ <properties>
+ <help>Client IP pool</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of IP pool</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_a-zA-Z0-9.]+</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="range">
+ <properties>
+ <help>Range of IP addresses</help>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>IPv4 prefix</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv4range</format>
+ <description>IPv4 address range inside /24 network</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv4-host"/>
+ <validator name="ipv4-range-mask" argument="-m 24 -r"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <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/interface-definitions/include/accel-ppp/default-pool.xml.i b/interface-definitions/include/accel-ppp/default-pool.xml.i
new file mode 100644
index 000000000..832594c12
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/default-pool.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from accel-ppp/default-pool.xml.i -->
+<leafNode name="default-pool">
+ <properties>
+ <help>Default client IP pool name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Default IP pool</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_a-zA-Z0-9.]+</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i b/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i
new file mode 100644
index 000000000..dcc58b97a
--- /dev/null
+++ b/interface-definitions/include/accel-ppp/gateway-address-multi.xml.i
@@ -0,0 +1,17 @@
+<!-- include start from accel-ppp/gateway-address-multi.xml.i -->
+<leafNode name="gateway-address">
+ <properties>
+ <help>Gateway IP address</help>
+ <constraintErrorMessage>invalid IPv4 address</constraintErrorMessage>
+ <valueHelp>
+ <format>ipv4net</format>
+ <description>Default Gateway, mask send to the client</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-prefix"/>
+ <validator name="ipv4-host"/>
+ </constraint>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/ipoe-server-version.xml.i b/interface-definitions/include/version/ipoe-server-version.xml.i
index 00d2544e6..e5983ab39 100644
--- a/interface-definitions/include/version/ipoe-server-version.xml.i
+++ b/interface-definitions/include/version/ipoe-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/ipoe-server-version.xml.i -->
-<syntaxVersion component='ipoe-server' version='1'></syntaxVersion>
+<syntaxVersion component='ipoe-server' version='2'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/l2tp-version.xml.i b/interface-definitions/include/version/l2tp-version.xml.i
index 86114d676..89edb160c 100644
--- a/interface-definitions/include/version/l2tp-version.xml.i
+++ b/interface-definitions/include/version/l2tp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/l2tp-version.xml.i -->
-<syntaxVersion component='l2tp' version='4'></syntaxVersion>
+<syntaxVersion component='l2tp' version='5'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i
index 6bdd8d75c..deed702f0 100644
--- a/interface-definitions/include/version/pppoe-server-version.xml.i
+++ b/interface-definitions/include/version/pppoe-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/pppoe-server-version.xml.i -->
-<syntaxVersion component='pppoe-server' version='6'></syntaxVersion>
+<syntaxVersion component='pppoe-server' version='7'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/pptp-version.xml.i b/interface-definitions/include/version/pptp-version.xml.i
index 0296c44e9..4386cedbd 100644
--- a/interface-definitions/include/version/pptp-version.xml.i
+++ b/interface-definitions/include/version/pptp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/pptp-version.xml.i -->
-<syntaxVersion component='pptp' version='2'></syntaxVersion>
+<syntaxVersion component='pptp' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/sstp-version.xml.i b/interface-definitions/include/version/sstp-version.xml.i
index 79b43a3e7..3ac54a3de 100644
--- a/interface-definitions/include/version/sstp-version.xml.i
+++ b/interface-definitions/include/version/sstp-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/sstp-version.xml.i -->
-<syntaxVersion component='sstp' version='4'></syntaxVersion>
+<syntaxVersion component='sstp' version='5'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/service-ipoe-server.xml.in b/interface-definitions/service-ipoe-server.xml.in
index 9ac0c8fdf..edfe6a34c 100644
--- a/interface-definitions/service-ipoe-server.xml.in
+++ b/interface-definitions/service-ipoe-server.xml.in
@@ -104,14 +104,8 @@
</tagNode>
#include <include/accel-ppp/max-concurrent-sessions.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
- <node name="client-ip-pool">
- <properties>
- <help>Client IP pools and gateway setting</help>
- </properties>
- <children>
- #include <include/accel-ppp/client-ip-pool-name.xml.i>
- </children>
- </node>
+ #include <include/accel-ppp/client-ip-pool.xml.i>
+ #include <include/accel-ppp/gateway-address-multi.xml.i>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
<node name="authentication">
<properties>
@@ -188,6 +182,7 @@
#include <include/accel-ppp/radius-additions.xml.i>
</children>
</node>
+ #include <include/accel-ppp/default-pool.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/service-pppoe-server.xml.in b/interface-definitions/service-pppoe-server.xml.in
index 44b689fe1..f1b369936 100644
--- a/interface-definitions/service-pppoe-server.xml.in
+++ b/interface-definitions/service-pppoe-server.xml.in
@@ -49,16 +49,7 @@
</node>
</children>
</node>
- <node name="client-ip-pool">
- <properties>
- <help>Pool of client IP addresses (must be within a /24)</help>
- </properties>
- <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-ip-pool.xml.i>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
<tagNode name="interface">
@@ -282,6 +273,7 @@
</leafNode>
</children>
</node>
+ #include <include/accel-ppp/default-pool.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index 60a1d323b..7980cfdf5 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -116,15 +116,7 @@
</children>
</node>
#include <include/accel-ppp/wins-server.xml.i>
- <node name="client-ip-pool">
- <properties>
- <help>Pool of client IP addresses (must be within a /24)</help>
- </properties>
- <children>
- #include <include/accel-ppp/client-ip-pool-start-stop.xml.i>
- #include <include/accel-ppp/client-ip-pool-subnet.xml.i>
- </children>
- </node>
+ #include <include/accel-ppp/client-ip-pool.xml.i>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
#include <include/generic-description.xml.i>
#include <include/dhcp-interface.xml.i>
@@ -253,6 +245,7 @@
#include <include/accel-ppp/ppp-options-ipv6-interface-id.xml.i>
</children>
</node>
+ #include <include/accel-ppp/default-pool.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn-pptp.xml.in b/interface-definitions/vpn-pptp.xml.in
index 964c4d21e..96f87f3e2 100644
--- a/interface-definitions/vpn-pptp.xml.in
+++ b/interface-definitions/vpn-pptp.xml.in
@@ -23,17 +23,10 @@
</constraint>
</properties>
</leafNode>
+ #include <include/accel-ppp/gateway-address.xml.i>
#include <include/name-server-ipv4.xml.i>
#include <include/accel-ppp/wins-server.xml.i>
- <node name="client-ip-pool">
- <properties>
- <help>Pool of client IP addresses (must be within a /24)</help>
- </properties>
- <children>
- #include <include/accel-ppp/client-ip-pool-start-stop.xml.i>
- </children>
- </node>
- #include <include/accel-ppp/gateway-address.xml.i>
+ #include <include/accel-ppp/client-ip-pool.xml.i>
<node name="authentication">
<properties>
<help>Authentication for remote access PPTP VPN</help>
@@ -118,6 +111,7 @@
#include <include/accel-ppp/radius-additions.xml.i>
</children>
</node>
+ #include <include/accel-ppp/default-pool.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vpn-sstp.xml.in b/interface-definitions/vpn-sstp.xml.in
index 9c818ba60..a1b69f990 100644
--- a/interface-definitions/vpn-sstp.xml.in
+++ b/interface-definitions/vpn-sstp.xml.in
@@ -29,19 +29,13 @@
#include <include/interface/mtu-68-1500.xml.i>
#include <include/accel-ppp/gateway-address.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
- <node name="client-ip-pool">
- <properties>
- <help>Client IP pools and gateway setting</help>
- </properties>
- <children>
- #include <include/accel-ppp/client-ip-pool-subnet.xml.i>
- </children>
- </node>
+ #include <include/accel-ppp/client-ip-pool.xml.i>
#include <include/accel-ppp/client-ipv6-pool.xml.i>
#include <include/port-number.xml.i>
<leafNode name="port">
<defaultValue>443</defaultValue>
</leafNode>
+ #include <include/accel-ppp/default-pool.xml.i>
<node name="ppp-options">
<properties>
<help>PPP (Point-to-Point Protocol) settings</help>
diff --git a/python/vyos/accel_ppp_util.py b/python/vyos/accel_ppp_util.py
new file mode 100644
index 000000000..757d447a2
--- /dev/null
+++ b/python/vyos/accel_ppp_util.py
@@ -0,0 +1,193 @@
+# Copyright 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
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# The sole purpose of this module is to hold common functions used in
+# all kinds of implementations to verify the CLI configuration.
+# It is started by migrating the interfaces to the new get_config_dict()
+# approach which will lead to a lot of code that can be reused.
+
+# NOTE: imports should be as local as possible to the function which
+# makes use of it!
+
+from vyos import ConfigError
+from vyos.utils.dict import dict_search
+
+
+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 or not pool_config["next_pool"]:
+ 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 ConfigError(
+ f"Pool '{next_pool_name}' not defined in configuration data"
+ )
+
+ if not resolved_pools:
+ raise ConfigError("Circular reference in configuration data")
+
+ for pool in resolved_pools:
+ unresolved_pools.pop(pool)
+
+ return pools
+
+
+def verify_accel_ppp_base_service(config, local_users=True):
+ """
+ Common helper function which must be used by all Accel-PPP services based
+ on get_config_dict()
+ """
+ # vertify auth settings
+ if local_users and dict_search("authentication.mode", config) == "local":
+ if (
+ dict_search("authentication.local_users", config) is None
+ or dict_search("authentication.local_users", config) == {}
+ ):
+ raise ConfigError(
+ "Authentication mode local requires local users to be configured!"
+ )
+
+ for user in dict_search("authentication.local_users.username", config):
+ user_config = config["authentication"]["local_users"]["username"][user]
+
+ if "password" not in user_config:
+ raise ConfigError(f'Password required for local user "{user}"')
+
+ if "rate_limit" in user_config:
+ # if up/download is set, check that both have a value
+ if not {"upload", "download"} <= set(user_config["rate_limit"]):
+ raise ConfigError(
+ f'User "{user}" has rate-limit configured for only one '
+ "direction but both upload and download must be given!"
+ )
+
+ elif dict_search("authentication.mode", config) == "radius":
+ if not dict_search("authentication.radius.server", config):
+ raise ConfigError("RADIUS authentication requires at least one server")
+
+ for server in dict_search("authentication.radius.server", config):
+ radius_config = config["authentication"]["radius"]["server"][server]
+ if "key" not in radius_config:
+ raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
+
+ if "name_server_ipv4" in config:
+ if len(config["name_server_ipv4"]) > 2:
+ raise ConfigError(
+ "Not more then two IPv4 DNS name-servers " "can be configured"
+ )
+
+ if "name_server_ipv6" in config:
+ if len(config["name_server_ipv6"]) > 3:
+ raise ConfigError(
+ "Not more then three IPv6 DNS name-servers " "can be configured"
+ )
+
+ if "client_ipv6_pool" in config:
+ ipv6_pool = config["client_ipv6_pool"]
+ if "delegate" in ipv6_pool:
+ if "prefix" not in ipv6_pool:
+ raise ConfigError(
+ 'IPv6 "delegate" also requires "prefix" to be defined!'
+ )
+
+ for delegate in ipv6_pool["delegate"]:
+ if "delegation_prefix" not in ipv6_pool["delegate"][delegate]:
+ raise ConfigError("delegation-prefix length required!")
+
+
+def verify_accel_ppp_ip_pool(vpn_config):
+ """
+ Common helper function which must be used by Accel-PPP
+ services (pptp, l2tp, sstp, pppoe) to verify client-ip-pool
+ """
+ if dict_search("client_ip_pool", vpn_config):
+ for pool_name, pool_config in vpn_config["client_ip_pool"].items():
+ next_pool = dict_search(f"next_pool", pool_config)
+ if next_pool:
+ if next_pool not in vpn_config["client_ip_pool"]:
+ raise ConfigError(f'Next pool "{next_pool}" does not exist')
+ if not dict_search(f"range", pool_config):
+ raise ConfigError(
+ f'Pool "{pool_name}" does not contain range but next-pool exists'
+ )
+
+ if not dict_search("gateway_address", vpn_config):
+ raise ConfigError("Server requires gateway-address to be configured!")
+ default_pool = dict_search("default_pool", vpn_config)
+ if default_pool:
+ if default_pool not in dict_search("client_ip_pool", vpn_config):
+ raise ConfigError(f'Default pool "{default_pool}" does not exists')
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 52f9238b8..27055c863 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -385,72 +385,6 @@ def verify_vlan_config(config):
verify_mtu_parent(c_vlan, config)
verify_mtu_parent(c_vlan, s_vlan)
-def verify_accel_ppp_base_service(config, local_users=True):
- """
- Common helper function which must be used by all Accel-PPP services based
- on get_config_dict()
- """
- # vertify auth settings
- if local_users and dict_search('authentication.mode', config) == 'local':
- if (dict_search(f'authentication.local_users', config) is None or
- dict_search(f'authentication.local_users', config) == {}):
- raise ConfigError(
- 'Authentication mode local requires local users to be configured!')
-
- for user in dict_search('authentication.local_users.username', config):
- user_config = config['authentication']['local_users']['username'][user]
-
- if 'password' not in user_config:
- raise ConfigError(f'Password required for local user "{user}"')
-
- if 'rate_limit' in user_config:
- # if up/download is set, check that both have a value
- if not {'upload', 'download'} <= set(user_config['rate_limit']):
- raise ConfigError(f'User "{user}" has rate-limit configured for only one ' \
- 'direction but both upload and download must be given!')
-
- elif dict_search('authentication.mode', config) == 'radius':
- if not dict_search('authentication.radius.server', config):
- raise ConfigError('RADIUS authentication requires at least one server')
-
- for server in dict_search('authentication.radius.server', config):
- radius_config = config['authentication']['radius']['server'][server]
- if 'key' not in radius_config:
- raise ConfigError(f'Missing RADIUS secret key for server "{server}"')
-
- # Check global gateway or gateway in named pool
- gateway = False
- if 'gateway_address' in config:
- gateway = True
- else:
- if 'client_ip_pool' in config:
- 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:
- if len(config['name_server_ipv4']) > 2:
- raise ConfigError('Not more then two IPv4 DNS name-servers ' \
- 'can be configured')
-
- if 'name_server_ipv6' in config:
- if len(config['name_server_ipv6']) > 3:
- raise ConfigError('Not more then three IPv6 DNS name-servers ' \
- 'can be configured')
-
- if 'client_ipv6_pool' in config:
- ipv6_pool = config['client_ipv6_pool']
- if 'delegate' in ipv6_pool:
- if 'prefix' not in ipv6_pool:
- raise ConfigError('IPv6 "delegate" also requires "prefix" to be defined!')
-
- for delegate in ipv6_pool['delegate']:
- if 'delegation_prefix' not in ipv6_pool['delegate'][delegate]:
- raise ConfigError('delegation-prefix length required!')
def verify_diffie_hellman_length(file, min_keysize):
""" Verify Diffie-Hellamn keypair length given via file. It must be greater
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py
index 989028f64..32624719f 100644
--- a/smoketest/scripts/cli/base_accel_ppp_test.py
+++ b/smoketest/scripts/cli/base_accel_ppp_test.py
@@ -11,10 +11,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
import re
import unittest
+
from base_vyostest_shim import VyOSUnitTestSHIM
from configparser import ConfigParser
@@ -25,12 +25,12 @@ from vyos.utils.system import get_half_cpus
from vyos.utils.process import process_named_running
from vyos.utils.process import cmd
+
class BasicAccelPPPTest:
class TestCase(VyOSUnitTestSHIM.TestCase):
-
@classmethod
def setUpClass(cls):
- cls._process_name = 'accel-pppd'
+ cls._process_name = "accel-pppd"
super(BasicAccelPPPTest.TestCase, cls).setUpClass()
@@ -39,7 +39,7 @@ class BasicAccelPPPTest:
cls.cli_delete(cls, cls._base_path)
def setUp(self):
- self._gateway = '192.0.2.1'
+ self._gateway = "192.0.2.1"
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
self.cli_delete(self._base_path)
@@ -60,84 +60,189 @@ class BasicAccelPPPTest:
def delete(self, path):
self.cli_delete(self._base_path + path)
- def basic_config(self):
- # PPPoE local auth mode requires local users to be configured!
- self.set(['authentication', 'local-users', 'username', 'vyos', 'password', 'vyos'])
- self.set(['authentication', 'mode', 'local'])
- self.set(['gateway-address', self._gateway])
+ def basic_protocol_specific_config(self):
+ """
+ An astract method.
+ Initialize protocol scpecific configureations.
+ """
+ self.assertFalse(True, msg="Function must be defined")
+
+ def initial_auth_config(self):
+ """
+ Initialization of default authentication for all protocols
+ """
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ "vyos",
+ "password",
+ "vyos",
+ ]
+ )
+ self.set(["authentication", "mode", "local"])
+
+ def initial_gateway_config(self):
+ """
+ Initialization of default gateway
+ """
+ self.set(["gateway-address", self._gateway])
+
+ def initial_pool_config(self):
+ """
+ Initialization of default client ip pool
+ """
+ first_pool = "SIMPLE-POOL"
+ self.set(["client-ip-pool", first_pool, "range", "192.0.2.0/24"])
+ self.set(["default-pool", first_pool])
+
+ def basic_config(self, is_auth=True, is_gateway=True, is_client_pool=True):
+ """
+ Initialization of basic configuration
+ :param is_auth: authentication initialization
+ :type is_auth: bool
+ :param is_gateway: gateway initialization
+ :type is_gateway: bool
+ :param is_client_pool: client ip pool initialization
+ :type is_client_pool: bool
+ """
+ self.basic_protocol_specific_config()
+ if is_auth:
+ self.initial_auth_config()
+ if is_gateway:
+ self.initial_gateway_config()
+ if is_client_pool:
+ self.initial_pool_config()
+
+ def getConfig(self, start, end="cli"):
+ """
+ Return part of configuration from line
+ where the first injection of start keyword to the line
+ where the first injection of end keyowrd
+ :param start: start keyword
+ :type start: str
+ :param end: end keyword
+ :type end: str
+ :return: part of config
+ :rtype: str
+ """
+ command = f'cat {self._config_file} | sed -n "/^\[{start}/,/^\[{end}/p"'
+ out = cmd(command)
+ return out
def verify(self, conf):
- self.assertEqual(conf['core']['thread-count'], str(get_half_cpus()))
+ self.assertEqual(conf["core"]["thread-count"], str(get_half_cpus()))
def test_accel_name_servers(self):
# Verify proper Name-Server configuration for IPv4 and IPv6
self.basic_config()
- nameserver = ['192.0.2.1', '192.0.2.2', '2001:db8::1']
+ nameserver = ["192.0.2.1", "192.0.2.2", "2001:db8::1"]
for ns in nameserver:
- self.set(['name-server', ns])
+ self.set(["name-server", ns])
# commit changes
self.cli_commit()
# Validate configuration values
- conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
conf.read(self._config_file)
# IPv4 and IPv6 nameservers must be checked individually
for ns in nameserver:
if is_ipv4(ns):
- self.assertIn(ns, [conf['dns']['dns1'], conf['dns']['dns2']])
+ self.assertIn(ns, [conf["dns"]["dns1"], conf["dns"]["dns2"]])
else:
- self.assertEqual(conf['ipv6-dns'][ns], None)
+ self.assertEqual(conf["ipv6-dns"][ns], None)
def test_accel_local_authentication(self):
# Test configuration of local authentication
self.basic_config()
# upload / download limit
- user = 'test'
- password = 'test2'
- static_ip = '100.100.100.101'
- upload = '5000'
- download = '10000'
-
- self.set(['authentication', 'local-users', 'username', user, 'password', password])
- self.set(['authentication', 'local-users', 'username', user, 'static-ip', static_ip])
- self.set(['authentication', 'local-users', 'username', user, 'rate-limit', 'upload', upload])
+ user = "test"
+ password = "test2"
+ static_ip = "100.100.100.101"
+ upload = "5000"
+ download = "10000"
+
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "password",
+ password,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "static-ip",
+ static_ip,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "rate-limit",
+ "upload",
+ upload,
+ ]
+ )
# upload rate-limit requires also download rate-limit
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.set(['authentication', 'local-users', 'username', user, 'rate-limit', 'download', download])
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "rate-limit",
+ "download",
+ download,
+ ]
+ )
# commit changes
self.cli_commit()
# Validate configuration values
- conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
conf.read(self._config_file)
# check proper path to chap-secrets file
- self.assertEqual(conf['chap-secrets']['chap-secrets'], self._chap_secrets)
+ self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets)
# basic verification
self.verify(conf)
# check local users
- tmp = cmd(f'sudo cat {self._chap_secrets}')
- regex = f'{user}\s+\*\s+{password}\s+{static_ip}\s+{download}/{upload}'
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s+{download}/{upload}"
tmp = re.findall(regex, tmp)
self.assertTrue(tmp)
# Check local-users default value(s)
- self.delete(['authentication', 'local-users', 'username', user, 'static-ip'])
+ self.delete(
+ ["authentication", "local-users", "username", user, "static-ip"]
+ )
# commit changes
self.cli_commit()
# check local users
- tmp = cmd(f'sudo cat {self._chap_secrets}')
- regex = f'{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}'
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}"
tmp = re.findall(regex, tmp)
self.assertTrue(tmp)
@@ -145,74 +250,170 @@ class BasicAccelPPPTest:
# Test configuration of RADIUS authentication for PPPoE server
self.basic_config()
- radius_server = '192.0.2.22'
- radius_key = 'secretVyOS'
- radius_port = '2000'
- radius_port_acc = '3000'
-
- self.set(['authentication', 'mode', 'radius'])
- self.set(['authentication', 'radius', 'server', radius_server, 'key', radius_key])
- self.set(['authentication', 'radius', 'server', radius_server, 'port', radius_port])
- self.set(['authentication', 'radius', 'server', radius_server, 'acct-port', radius_port_acc])
-
- coa_server = '4.4.4.4'
- coa_key = 'testCoA'
- self.set(['authentication', 'radius', 'dynamic-author', 'server', coa_server])
- self.set(['authentication', 'radius', 'dynamic-author', 'key', coa_key])
-
- nas_id = 'VyOS-PPPoE'
- nas_ip = '7.7.7.7'
- self.set(['authentication', 'radius', 'nas-identifier', nas_id])
- self.set(['authentication', 'radius', 'nas-ip-address', nas_ip])
-
- source_address = '1.2.3.4'
- self.set(['authentication', 'radius', 'source-address', source_address])
+ radius_server = "192.0.2.22"
+ radius_key = "secretVyOS"
+ radius_port = "2000"
+ radius_port_acc = "3000"
+
+ self.set(["authentication", "mode", "radius"])
+ self.set(
+ ["authentication", "radius", "server", radius_server, "key", radius_key]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "port",
+ radius_port,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "acct-port",
+ radius_port_acc,
+ ]
+ )
+
+ coa_server = "4.4.4.4"
+ coa_key = "testCoA"
+ self.set(
+ ["authentication", "radius", "dynamic-author", "server", coa_server]
+ )
+ self.set(["authentication", "radius", "dynamic-author", "key", coa_key])
+
+ nas_id = "VyOS-PPPoE"
+ nas_ip = "7.7.7.7"
+ self.set(["authentication", "radius", "nas-identifier", nas_id])
+ self.set(["authentication", "radius", "nas-ip-address", nas_ip])
+
+ source_address = "1.2.3.4"
+ self.set(["authentication", "radius", "source-address", source_address])
# commit changes
self.cli_commit()
# Validate configuration values
- conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
conf.read(self._config_file)
# basic verification
self.verify(conf)
# check auth
- self.assertTrue(conf['radius'].getboolean('verbose'))
- self.assertEqual(conf['radius']['acct-timeout'], '3')
- self.assertEqual(conf['radius']['timeout'], '3')
- self.assertEqual(conf['radius']['max-try'], '3')
-
- self.assertEqual(conf['radius']['dae-server'], f'{coa_server}:1700,{coa_key}')
- self.assertEqual(conf['radius']['nas-identifier'], nas_id)
- self.assertEqual(conf['radius']['nas-ip-address'], nas_ip)
- self.assertEqual(conf['radius']['bind'], source_address)
-
- server = conf['radius']['server'].split(',')
+ self.assertTrue(conf["radius"].getboolean("verbose"))
+ self.assertEqual(conf["radius"]["acct-timeout"], "3")
+ self.assertEqual(conf["radius"]["timeout"], "3")
+ self.assertEqual(conf["radius"]["max-try"], "3")
+
+ self.assertEqual(
+ conf["radius"]["dae-server"], f"{coa_server}:1700,{coa_key}"
+ )
+ self.assertEqual(conf["radius"]["nas-identifier"], nas_id)
+ self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip)
+ self.assertEqual(conf["radius"]["bind"], source_address)
+
+ server = conf["radius"]["server"].split(",")
self.assertEqual(radius_server, server[0])
self.assertEqual(radius_key, server[1])
- self.assertEqual(f'auth-port={radius_port}', server[2])
- self.assertEqual(f'acct-port={radius_port_acc}', server[3])
- self.assertEqual(f'req-limit=0', server[4])
- self.assertEqual(f'fail-time=0', server[5])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"acct-port={radius_port_acc}", server[3])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
#
# Disable Radius Accounting
#
- self.delete(['authentication', 'radius', 'server', radius_server, 'acct-port'])
- self.set(['authentication', 'radius', 'server', radius_server, 'disable-accounting'])
+ self.delete(
+ ["authentication", "radius", "server", radius_server, "acct-port"]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "disable-accounting",
+ ]
+ )
# commit changes
self.cli_commit()
conf.read(self._config_file)
- server = conf['radius']['server'].split(',')
+ server = conf["radius"]["server"].split(",")
self.assertEqual(radius_server, server[0])
self.assertEqual(radius_key, server[1])
- self.assertEqual(f'auth-port={radius_port}', server[2])
- self.assertEqual(f'acct-port=0', server[3])
- self.assertEqual(f'req-limit=0', server[4])
- self.assertEqual(f'fail-time=0', server[5])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"acct-port=0", server[3])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
+
+ def test_accel_ipv4_pool(self):
+ """
+ Test accel-ppp IPv4 pool
+ """
+ self.basic_config(is_gateway=False, is_client_pool=False)
+ gateway = "192.0.2.1"
+ subnet = "172.16.0.0/24"
+ first_pool = "POOL1"
+ second_pool = "POOL2"
+ range = "192.0.2.10-192.0.2.20"
+
+ self.set(["gateway-address", gateway])
+ self.set(["client-ip-pool", first_pool, "range", subnet])
+ self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
+ self.set(["client-ip-pool", second_pool, "range", range])
+ self.set(["default-pool", first_pool])
+ # commit changes
+
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ self.assertEqual(
+ f"{first_pool},next={second_pool}", conf["ip-pool"][f"{subnet},name"]
+ )
+ self.assertEqual(second_pool, conf["ip-pool"][f"{range},name"])
+ self.assertEqual(gateway, conf["ip-pool"]["gw-ip-address"])
+ self.assertEqual(first_pool, conf[self._protocol_section]["ip-pool"])
+
+ def test_accel_next_pool(self):
+ """
+ T5099 required specific order
+ """
+ self.basic_config(is_gateway=False, is_client_pool=False)
+
+ gateway = "192.0.2.1"
+ first_pool = "VyOS-pool1"
+ first_subnet = "192.0.2.0/25"
+ second_pool = "Vyos-pool2"
+ second_subnet = "203.0.113.0/25"
+ third_pool = "Vyos-pool3"
+ third_subnet = "198.51.100.0/24"
+
+ self.set(["gateway-address", gateway])
+ self.set(["client-ip-pool", first_pool, "range", first_subnet])
+ self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
+ self.set(["client-ip-pool", second_pool, "range", second_subnet])
+ self.set(["client-ip-pool", second_pool, "next-pool", third_pool])
+ self.set(["client-ip-pool", third_pool, "range", third_subnet])
+
+ # commit changes
+ self.cli_commit()
+
+ config = self.getConfig("ip-pool")
+ pool_config = f"""gw-ip-address={gateway}
+{third_subnet},name={third_pool}
+{second_subnet},name={second_pool},next={third_pool}
+{first_subnet},name={first_pool},next={second_pool}"""
+ self.assertIn(pool_config, config)
diff --git a/smoketest/scripts/cli/test_service_ipoe-server.py b/smoketest/scripts/cli/test_service_ipoe-server.py
index 4dd3e761c..358668e0d 100755
--- a/smoketest/scripts/cli/test_service_ipoe-server.py
+++ b/smoketest/scripts/cli/test_service_ipoe-server.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 VyOS maintainers and contributors
+# Copyright (C) 2022-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
@@ -17,28 +17,35 @@
import re
import unittest
+from collections import OrderedDict
from base_accel_ppp_test import BasicAccelPPPTest
from vyos.configsession import ConfigSessionError
from vyos.utils.process import cmd
-
from configparser import ConfigParser
+from configparser import RawConfigParser
-ac_name = 'ACN'
-interface = 'eth0'
+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 MultiOrderedDict(OrderedDict):
+ # Accel-ppp has duplicate keys in config file (gw-ip-address)
+ # This class is used to define dictionary which can contain multiple values
+ # in one key.
+ def __setitem__(self, key, value):
+ if isinstance(value, list) and key in self:
+ self[key].extend(value)
+ else:
+ super(OrderedDict, self).__setitem__(key, value)
class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
@classmethod
def setUpClass(cls):
- cls._base_path = ['service', 'ipoe-server']
- cls._config_file = '/run/accel-pppd/ipoe.conf'
- cls._chap_secrets = '/run/accel-pppd/ipoe.chap-secrets'
+ cls._base_path = ["service", "ipoe-server"]
+ cls._config_file = "/run/accel-pppd/ipoe.conf"
+ cls._chap_secrets = "/run/accel-pppd/ipoe.chap-secrets"
+ cls._protocol_section = "ipoe"
# call base-classes classmethod
super(TestServiceIPoEServer, cls).setUpClass()
@@ -47,22 +54,29 @@ class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
super().verify(conf)
# Validate configuration values
- accel_modules = list(conf['modules'].keys())
- self.assertIn('log_syslog', accel_modules)
- self.assertIn('ipoe', accel_modules)
- self.assertIn('shaper', accel_modules)
- self.assertIn('ipv6pool', accel_modules)
- self.assertIn('ipv6_nd', accel_modules)
- self.assertIn('ipv6_dhcp', accel_modules)
- self.assertIn('ippool', accel_modules)
-
- def basic_config(self):
- self.set(['interface', interface, 'client-subnet', '192.168.0.0/24'])
+ accel_modules = list(conf["modules"].keys())
+ self.assertIn("log_syslog", accel_modules)
+ self.assertIn("ipoe", accel_modules)
+ self.assertIn("shaper", accel_modules)
+ self.assertIn("ipv6pool", accel_modules)
+ self.assertIn("ipv6_nd", accel_modules)
+ self.assertIn("ipv6_dhcp", accel_modules)
+ self.assertIn("ippool", accel_modules)
+
+ def initial_gateway_config(self):
+ self._gateway = "192.0.2.1/24"
+ super().initial_gateway_config()
+
+ def initial_auth_config(self):
+ self.set(["authentication", "mode", "noauth"])
+
+ def basic_protocol_specific_config(self):
+ self.set(["interface", interface, "client-subnet", "192.168.0.0/24"])
def test_accel_local_authentication(self):
- mac_address = '08:00:27:2f:d8:06'
- self.set(['authentication', 'interface', interface, 'mac', mac_address])
- self.set(['authentication', 'mode', 'local'])
+ mac_address = "08:00:27:2f:d8:06"
+ self.set(["authentication", "interface", interface, "mac", mac_address])
+ self.set(["authentication", "mode", "local"])
# No IPoE interface configured
with self.assertRaises(ConfigSessionError):
@@ -70,115 +84,109 @@ class TestServiceIPoEServer(BasicAccelPPPTest.TestCase):
# Test configuration of local authentication for PPPoE server
self.basic_config()
-
+ # Rewrite authentication from basic_config
+ self.set(["authentication", "interface", interface, "mac", mac_address])
+ self.set(["authentication", "mode", "local"])
# commit changes
self.cli_commit()
# Validate configuration values
- conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
conf.read(self._config_file)
# check proper path to chap-secrets file
- self.assertEqual(conf['chap-secrets']['chap-secrets'], self._chap_secrets)
+ self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets)
- accel_modules = list(conf['modules'].keys())
- self.assertIn('chap-secrets', accel_modules)
+ accel_modules = list(conf["modules"].keys())
+ self.assertIn("chap-secrets", accel_modules)
# basic verification
self.verify(conf)
# check local users
- tmp = cmd(f'sudo cat {self._chap_secrets}')
- regex = f'{interface}\s+\*\s+{mac_address}\s+\*'
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{interface}\s+\*\s+{mac_address}\s+\*"
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])
+ def test_accel_ipv4_pool(self):
+ self.basic_config(is_gateway=False, is_client_pool=False)
+ gateway = ["172.16.0.1/25", "192.0.2.1/24"]
+ subnet = "172.16.0.0/24"
+ first_pool = "POOL1"
+ second_pool = "POOL2"
+ range = "192.0.2.10-192.0.2.20"
+
+ for gw in gateway:
+ self.set(["gateway-address", gw])
+
+ self.set(["client-ip-pool", first_pool, "range", subnet])
+ self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
+ self.set(["client-ip-pool", second_pool, "range", range])
+ self.set(["default-pool", first_pool])
# commit changes
- self.cli_commit()
+ self.cli_commit()
# Validate configuration values
- conf = ConfigParser(allow_no_value=True, delimiters='=', strict=False)
+ conf = RawConfigParser(
+ allow_no_value=True,
+ delimiters="=",
+ strict=False,
+ dict_type=MultiOrderedDict,
+ )
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)
+ self.assertIn(
+ f"{first_pool},next={second_pool}", conf["ip-pool"][f"{subnet},name"]
+ )
+ self.assertIn(second_pool, conf["ip-pool"][f"{range},name"])
+
+ gw_pool_config_list = conf.get("ip-pool", "gw-ip-address")
+ gw_ipoe_config_list = conf.get(self._protocol_section, "gw-ip-address")
+ for gw in gateway:
+ self.assertIn(gw.split("/")[0], gw_pool_config_list)
+ self.assertIn(gw, gw_ipoe_config_list)
+ self.assertIn(first_pool, conf[self._protocol_section]["ip-pool"])
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])
+ self.basic_config(is_gateway=False, is_client_pool=False)
+
+ first_pool = "VyOS-pool1"
+ first_subnet = "192.0.2.0/25"
+ first_gateway = "192.0.2.1/24"
+ second_pool = "Vyos-pool2"
+ second_subnet = "203.0.113.0/25"
+ second_gateway = "203.0.113.1/24"
+ third_pool = "Vyos-pool3"
+ third_subnet = "198.51.100.0/24"
+ third_gateway = "198.51.100.1/24"
+
+ self.set(["gateway-address", f"{first_gateway}"])
+ self.set(["gateway-address", f"{second_gateway}"])
+ self.set(["gateway-address", f"{third_gateway}"])
+
+ self.set(["client-ip-pool", first_pool, "range", first_subnet])
+ self.set(["client-ip-pool", first_pool, "next-pool", second_pool])
+ self.set(["client-ip-pool", second_pool, "range", second_subnet])
+ self.set(["client-ip-pool", second_pool, "next-pool", third_pool])
+ self.set(["client-ip-pool", third_pool, "range", third_subnet])
# 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]')
+ config = self.getConfig("ip-pool")
# T5099 required specific order
- pool_config = f'''{third_subnet},name={third_pool}
+ pool_config = f"""gw-ip-address={first_gateway.split('/')[0]}
+gw-ip-address={second_gateway.split('/')[0]}
+gw-ip-address={third_gateway.split('/')[0]}
+{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'''
+{first_subnet},name={first_pool},next={second_pool}"""
self.assertIn(pool_config, config)
-if __name__ == '__main__':
+if __name__ == "__main__":
unittest.main(verbosity=2)
-
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 969abd3d5..3001e71bf 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -32,7 +32,7 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
cls._base_path = ['service', 'pppoe-server']
cls._config_file = '/run/accel-pppd/pppoe.conf'
cls._chap_secrets = '/run/accel-pppd/pppoe.chap-secrets'
-
+ cls._protocol_section = 'pppoe'
# call base-classes classmethod
super(TestServicePPPoEServer, cls).setUpClass()
@@ -65,13 +65,11 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
super().verify(conf)
- def basic_config(self):
+ def basic_protocol_specific_config(self):
self.cli_set(local_if + ['address', '192.0.2.1/32'])
-
self.set(['access-concentrator', ac_name])
self.set(['interface', interface])
- super().basic_config()
def test_pppoe_server_ppp_options(self):
# Test configuration of local authentication for PPPoE server
@@ -120,7 +118,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
# check interface-cache
self.assertEqual(conf['ppp']['unit-cache'], interface_cache)
-
def test_pppoe_server_authentication_protocols(self):
# Test configuration of local authentication for PPPoE server
self.basic_config()
@@ -137,68 +134,25 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
self.assertEqual(conf['modules']['auth_mschap_v2'], None)
-
- def test_pppoe_server_client_ip_pool(self):
- # Test configuration of IPv6 client pools
- self.basic_config()
-
- subnet = '172.18.0.0/24'
+ def test_pppoe_server_shaper(self):
fwmark = '223'
limiter = 'tbf'
+ self.basic_config()
- self.set(['client-ip-pool', 'subnet', subnet])
-
- start = '192.0.2.10'
- stop = '192.0.2.20'
- stop_octet = stop.split('.')[3]
- start_stop = f'{start}-{stop_octet}'
- self.set(['client-ip-pool', 'start', start])
- self.set(['client-ip-pool', 'stop', stop])
self.set(['shaper', 'fwmark', fwmark])
-
# commit changes
- self.cli_commit()
-
- # Validate configuration values
- conf = ConfigParser(allow_no_value=True)
- conf.read(self._config_file)
- # check configured subnet
- self.assertEqual(conf['ip-pool'][subnet], None)
- self.assertEqual(conf['ip-pool'][start_stop], None)
- self.assertEqual(conf['ip-pool']['gw-ip-address'], self._gateway)
- self.assertEqual(conf['shaper']['fwmark'], fwmark)
- self.assertEqual(conf['shaper']['down-limiter'], limiter)
-
-
- 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)
+ # basic verification
+ self.verify(conf)
+ self.assertEqual(conf['shaper']['fwmark'], fwmark)
+ self.assertEqual(conf['shaper']['down-limiter'], limiter)
def test_pppoe_server_client_ipv6_pool(self):
# Test configuration of IPv6 client pools
@@ -239,7 +193,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
self.assertEqual(conf['ipv6-pool'][client_prefix], None)
self.assertEqual(conf['ipv6-pool']['delegate'], f'{delegate_prefix},{delegate_mask}')
-
def test_accel_radius_authentication(self):
radius_called_sid = 'ifname:mac'
radius_acct_interim_jitter = '9'
@@ -261,7 +214,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
self.assertEqual(conf['radius']['acct-interim-jitter'], radius_acct_interim_jitter)
self.assertEqual(conf['radius']['acct-interim-interval'], radius_acct_interim_interval)
-
def test_pppoe_server_vlan(self):
vlans = ['100', '200', '300-310']
@@ -284,5 +236,6 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
tmp = ','.join(vlans)
self.assertIn(f'vlan-mon={interface},{tmp}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_l2tp.py b/smoketest/scripts/cli/test_vpn_l2tp.py
new file mode 100755
index 000000000..05ffb6bb5
--- /dev/null
+++ b/smoketest/scripts/cli/test_vpn_l2tp.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+import re
+import unittest
+
+from base_accel_ppp_test import BasicAccelPPPTest
+from configparser import ConfigParser
+from vyos.utils.process import cmd
+
+
+class TestVPNL2TPServer(BasicAccelPPPTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._base_path = ['vpn', 'l2tp', 'remote-access']
+ cls._config_file = '/run/accel-pppd/l2tp.conf'
+ cls._chap_secrets = '/run/accel-pppd/l2tp.chap-secrets'
+ cls._protocol_section = 'l2tp'
+ # call base-classes classmethod
+ super(TestVPNL2TPServer, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestVPNL2TPServer, cls).tearDownClass()
+
+ def basic_protocol_specific_config(self):
+ pass
+
+ def test_accel_local_authentication(self):
+ # Test configuration of local authentication
+ self.basic_config()
+
+ # upload / download limit
+ user = "test"
+ password = "test2"
+ static_ip = "100.100.100.101"
+ upload = "5000"
+ download = "10000"
+
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "password",
+ password,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "static-ip",
+ static_ip,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "rate-limit",
+ "upload",
+ upload,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "rate-limit",
+ "download",
+ download,
+ ]
+ )
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ # check proper path to chap-secrets file
+ self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets)
+
+ # basic verification
+ self.verify(conf)
+
+ # check local users
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s+{download}/{upload}"
+ tmp = re.findall(regex, tmp)
+ self.assertTrue(tmp)
+
+ # Check local-users default value(s)
+ self.delete(["authentication", "local-users", "username", user, "static-ip"])
+ # commit changes
+ self.cli_commit()
+
+ # check local users
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+\*\s+{download}/{upload}"
+ tmp = re.findall(regex, tmp)
+ self.assertTrue(tmp)
+
+ def test_accel_radius_authentication(self):
+ # Test configuration of RADIUS authentication for PPPoE server
+ self.basic_config()
+
+ radius_server = "192.0.2.22"
+ radius_key = "secretVyOS"
+ radius_port = "2000"
+
+ self.set(["authentication", "mode", "radius"])
+ self.set(
+ ["authentication", "radius", "server", radius_server, "key", radius_key]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "port",
+ radius_port,
+ ]
+ )
+
+
+ nas_id = "VyOS-PPPoE"
+ nas_ip = "7.7.7.7"
+ self.set(["authentication", "radius", "nas-identifier", nas_id])
+ self.set(["authentication", "radius", "nas-ip-address", nas_ip])
+
+ source_address = "1.2.3.4"
+ self.set(["authentication", "radius", "source-address", source_address])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ # basic verification
+ self.verify(conf)
+
+ # check auth
+ self.assertTrue(conf["radius"].getboolean("verbose"))
+ self.assertEqual(conf["radius"]["acct-timeout"], "3")
+ self.assertEqual(conf["radius"]["timeout"], "3")
+ self.assertEqual(conf["radius"]["max-try"], "3")
+
+ self.assertEqual(conf["radius"]["nas-identifier"], nas_id)
+ self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip)
+ self.assertEqual(conf["radius"]["bind"], source_address)
+
+ server = conf["radius"]["server"].split(",")
+ self.assertEqual(radius_server, server[0])
+ self.assertEqual(radius_key, server[1])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
+
+ #
+ # Disable Radius Accounting
+ #
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "disable-accounting",
+ ]
+ )
+
+ # commit changes
+ self.cli_commit()
+
+ conf.read(self._config_file)
+
+ server = conf["radius"]["server"].split(",")
+ self.assertEqual(radius_server, server[0])
+ self.assertEqual(radius_key, server[1])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"acct-port=0", server[3])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_pptp.py b/smoketest/scripts/cli/test_vpn_pptp.py
new file mode 100755
index 000000000..0d9ea312e
--- /dev/null
+++ b/smoketest/scripts/cli/test_vpn_pptp.py
@@ -0,0 +1,223 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+import re
+import unittest
+
+from configparser import ConfigParser
+from vyos.utils.process import cmd
+from base_accel_ppp_test import BasicAccelPPPTest
+from vyos.template import is_ipv4
+
+
+class TestVPNPPTPServer(BasicAccelPPPTest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ cls._base_path = ['vpn', 'pptp', 'remote-access']
+ cls._config_file = '/run/accel-pppd/pptp.conf'
+ cls._chap_secrets = '/run/accel-pppd/pptp.chap-secrets'
+ cls._protocol_section = 'pptp'
+ # call base-classes classmethod
+ super(TestVPNPPTPServer, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestVPNPPTPServer, cls).tearDownClass()
+
+ def basic_protocol_specific_config(self):
+ pass
+
+ def test_accel_name_servers(self):
+ # Verify proper Name-Server configuration for IPv4
+ self.basic_config()
+
+ nameserver = ["192.0.2.1", "192.0.2.2"]
+ for ns in nameserver:
+ self.set(["name-server", ns])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ # IPv4 and IPv6 nameservers must be checked individually
+ for ns in nameserver:
+ self.assertIn(ns, [conf["dns"]["dns1"], conf["dns"]["dns2"]])
+
+ def test_accel_local_authentication(self):
+ # Test configuration of local authentication
+ self.basic_config()
+
+ # upload / download limit
+ user = "test"
+ password = "test2"
+ static_ip = "100.100.100.101"
+ upload = "5000"
+ download = "10000"
+
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "password",
+ password,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "local-users",
+ "username",
+ user,
+ "static-ip",
+ static_ip,
+ ]
+ )
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ # check proper path to chap-secrets file
+ self.assertEqual(conf["chap-secrets"]["chap-secrets"], self._chap_secrets)
+
+ # basic verification
+ self.verify(conf)
+
+ # check local users
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+{static_ip}\s"
+ tmp = re.findall(regex, tmp)
+ self.assertTrue(tmp)
+
+ # Check local-users default value(s)
+ self.delete(["authentication", "local-users", "username", user, "static-ip"])
+ # commit changes
+ self.cli_commit()
+
+ # check local users
+ tmp = cmd(f"sudo cat {self._chap_secrets}")
+ regex = f"{user}\s+\*\s+{password}\s+\*\s"
+ tmp = re.findall(regex, tmp)
+ self.assertTrue(tmp)
+
+ def test_accel_radius_authentication(self):
+ # Test configuration of RADIUS authentication for PPPoE server
+ self.basic_config()
+
+ radius_server = "192.0.2.22"
+ radius_key = "secretVyOS"
+ radius_port = "2000"
+ radius_port_acc = "3000"
+
+ self.set(["authentication", "mode", "radius"])
+ self.set(
+ ["authentication", "radius", "server", radius_server, "key", radius_key]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "port",
+ radius_port,
+ ]
+ )
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "acct-port",
+ radius_port_acc,
+ ]
+ )
+
+ nas_id = "VyOS-PPPoE"
+ nas_ip = "7.7.7.7"
+ self.set(["authentication", "radius", "nas-identifier", nas_id])
+ self.set(["authentication", "radius", "nas-ip-address", nas_ip])
+
+ source_address = "1.2.3.4"
+ self.set(["authentication", "radius", "source-address", source_address])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True, delimiters="=", strict=False)
+ conf.read(self._config_file)
+
+ # basic verification
+ self.verify(conf)
+
+ # check auth
+ self.assertTrue(conf["radius"].getboolean("verbose"))
+ self.assertEqual(conf["radius"]["acct-timeout"], "30")
+ self.assertEqual(conf["radius"]["timeout"], "30")
+ self.assertEqual(conf["radius"]["max-try"], "3")
+
+ self.assertEqual(conf["radius"]["nas-identifier"], nas_id)
+ self.assertEqual(conf["radius"]["nas-ip-address"], nas_ip)
+ self.assertEqual(conf["radius"]["bind"], source_address)
+
+ server = conf["radius"]["server"].split(",")
+ self.assertEqual(radius_server, server[0])
+ self.assertEqual(radius_key, server[1])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"acct-port={radius_port_acc}", server[3])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
+
+ #
+ # Disable Radius Accounting
+ #
+ self.delete(["authentication", "radius", "server", radius_server, "acct-port"])
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "disable-accounting",
+ ]
+ )
+
+ # commit changes
+ self.cli_commit()
+
+ conf.read(self._config_file)
+
+ server = conf["radius"]["server"].split(",")
+ self.assertEqual(radius_server, server[0])
+ self.assertEqual(radius_key, server[1])
+ self.assertEqual(f"auth-port={radius_port}", server[2])
+ self.assertEqual(f"acct-port=0", server[3])
+ self.assertEqual(f"req-limit=0", server[4])
+ self.assertEqual(f"fail-time=0", server[5])
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py
index 232eafcf2..f0695d577 100755
--- a/smoketest/scripts/cli/test_vpn_sstp.py
+++ b/smoketest/scripts/cli/test_vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 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
@@ -47,7 +47,7 @@ class TestVPNSSTPServer(BasicAccelPPPTest.TestCase):
cls._base_path = ['vpn', 'sstp']
cls._config_file = '/run/accel-pppd/sstp.conf'
cls._chap_secrets = '/run/accel-pppd/sstp.chap-secrets'
-
+ cls._protocol_section = 'sstp'
# call base-classes classmethod
super(TestVPNSSTPServer, cls).setUpClass()
@@ -58,26 +58,23 @@ class TestVPNSSTPServer(BasicAccelPPPTest.TestCase):
@classmethod
def tearDownClass(cls):
cls.cli_delete(cls, pki_path)
-
super(TestVPNSSTPServer, cls).tearDownClass()
- def basic_config(self):
- # SSL is mandatory
+ def basic_protocol_specific_config(self):
self.set(['ssl', 'ca-certificate', 'sstp'])
self.set(['ssl', 'certificate', 'sstp'])
- self.set(['client-ip-pool', 'subnet', '192.0.2.0/24'])
-
- super().basic_config()
def test_accel_local_authentication(self):
# Change default port
port = '8443'
self.set(['port', port])
+ self.basic_config()
super().test_accel_local_authentication()
config = read_file(self._config_file)
self.assertIn(f'port={port}', 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 b70e32373..36f00dec5 100755
--- a/src/conf_mode/service_ipoe-server.py
+++ b/src/conf_mode/service_ipoe-server.py
@@ -15,17 +15,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import jmespath
from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import get_pools_in_order
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -35,87 +35,6 @@ 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
@@ -128,18 +47,11 @@ 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)
+ if dict_search('client_ip_pool', 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
+ ipoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', ipoe))
+ ipoe['server_type'] = 'ipoe'
return ipoe
@@ -156,9 +68,7 @@ def verify(ipoe):
raise ConfigError('Option "client-subnet" incompatible with "vlan"!'
'Use "ipoe client-ip-pool" instead.')
- #verify_accel_ppp_base_service(ipoe, local_users=False)
- # IPoE server does not have 'gateway' option in the CLI
- # we cannot use configverify.py verify_accel_ppp_base_service for ipoe-server
+ verify_accel_ppp_ip_pool(ipoe)
if dict_search('authentication.mode', ipoe) == 'radius':
if not dict_search('authentication.radius.server', ipoe):
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 87660c127..7c624f034 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -21,13 +21,16 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
from vyos.configdict import is_node_changed
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.configverify import verify_interface_exists
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_base_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
pppoe_conf = r'/run/accel-pppd/pppoe.conf'
@@ -45,6 +48,10 @@ def get_config(config=None):
# retrieve common dictionary keys
pppoe = get_accel_dict(conf, base, pppoe_chap_secrets)
+ if dict_search('client_ip_pool', pppoe):
+ # Multiple named pools require ordered values T5099
+ pppoe['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pppoe))
+
# reload-or-restart does not implemented in accel-ppp
# use this workaround until it will be implemented
# https://phabricator.accel-ppp.org/T3
@@ -53,7 +60,7 @@ def get_config(config=None):
is_node_changed(conf, base + ['interface'])]
if any(conditions):
pppoe.update({'restart_required': {}})
-
+ pppoe['server_type'] = 'pppoe'
return pppoe
def verify(pppoe):
@@ -72,12 +79,7 @@ def verify(pppoe):
for interface in pppoe['interface']:
verify_interface_exists(interface)
- # local ippool and gateway settings config checks
- if not (dict_search('client_ip_pool.subnet', pppoe) or
- (dict_search('client_ip_pool.name', pppoe) or
- (dict_search('client_ip_pool.start', pppoe) and
- dict_search('client_ip_pool.stop', pppoe)))):
- print('Warning: No PPPoE client pool defined')
+ verify_accel_ppp_ip_pool(pppoe)
if dict_search('authentication.radius.dynamic_author.server', pppoe):
if not dict_search('authentication.radius.dynamic_author.key', pppoe):
diff --git a/src/conf_mode/vpn_l2tp.py b/src/conf_mode/vpn_l2tp.py
index 6232ce64a..9a022d93c 100755
--- a/src/conf_mode/vpn_l2tp.py
+++ b/src/conf_mode/vpn_l2tp.py
@@ -21,15 +21,16 @@ from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
-from ipaddress import ip_network
-
from vyos.config import Config
from vyos.template import is_ipv4
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.system import get_half_cpus
+from vyos.utils.dict import dict_search
from vyos.utils.network import check_port_availability
from vyos.utils.network import is_listen_port_bind_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
@@ -43,7 +44,7 @@ default_config_data = {
'auth_ppp_mppe': 'prefer',
'auth_proto': ['auth_mschap_v2'],
'chap_secrets_file': l2tp_chap_secrets, # used in Jinja2 template
- 'client_ip_pool': None,
+ 'client_ip_pool': {},
'client_ip_subnets': [],
'client_ipv6_pool': [],
'client_ipv6_pool_configured': False,
@@ -246,13 +247,14 @@ def get_config(config=None):
conf.set_level(base_path)
if conf.exists(['client-ip-pool']):
- if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
- start = conf.return_value(['client-ip-pool', 'start'])
- stop = conf.return_value(['client-ip-pool', 'stop'])
- l2tp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+ for pool_name in conf.list_nodes(['client-ip-pool']):
+ l2tp['client_ip_pool'][pool_name] = {}
+ l2tp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range'])
+ l2tp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool'])
- if conf.exists(['client-ip-pool', 'subnet']):
- l2tp['client_ip_subnets'] = conf.return_values(['client-ip-pool', 'subnet'])
+ if dict_search('client_ip_pool', l2tp):
+ # Multiple named pools require ordered values T5099
+ l2tp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', l2tp))
if conf.exists(['client-ipv6-pool', 'prefix']):
l2tp['client_ipv6_pool_configured'] = True
@@ -281,23 +283,15 @@ def get_config(config=None):
l2tp['client_ipv6_delegate_prefix'].append(tmp)
+ if conf.exists(['default-pool']):
+ l2tp['default_pool'] = conf.return_value(['default-pool'])
+
if conf.exists(['mtu']):
l2tp['mtu'] = conf.return_value(['mtu'])
# gateway address
if conf.exists(['gateway-address']):
l2tp['gateway_address'] = conf.return_value(['gateway-address'])
- else:
- # calculate gw-ip-address
- if conf.exists(['client-ip-pool', 'start']):
- # use start ip as gw-ip-address
- l2tp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
-
- elif conf.exists(['client-ip-pool', 'subnet']):
- # use first ip address from first defined pool
- subnet = conf.return_values(['client-ip-pool', 'subnet'])[0]
- subnet = ip_network(subnet)
- l2tp['gateway_address'] = str(list(subnet.hosts())[0])
# LNS secret
if conf.exists(['lns', 'shared-secret']):
@@ -330,9 +324,13 @@ def get_config(config=None):
if conf.exists(['ppp-options', 'ipv6-peer-intf-id']):
l2tp['ppp_ipv6_peer_intf_id'] = conf.return_value(['ppp-options', 'ipv6-peer-intf-id'])
+ l2tp['server_type'] = 'l2tp'
return l2tp
+
+
+
def verify(l2tp):
if not l2tp:
return None
@@ -366,10 +364,11 @@ def verify(l2tp):
not is_listen_port_bind_service(int(port), 'accel-pppd'):
raise ConfigError(f'"{proto}" port "{port}" is used by another service')
- # check for the existence of a client ip pool
- if not (l2tp['client_ip_pool'] or l2tp['client_ip_subnets']):
- raise ConfigError(
- "set vpn l2tp remote-access client-ip-pool requires subnet or start/stop IP pool")
+ if l2tp['auth_mode'] == 'local' or l2tp['auth_mode'] == 'noauth':
+ if not l2tp['client_ip_pool']:
+ raise ConfigError(
+ "L2TP local auth mode requires local client-ip-pool to be configured!")
+ verify_accel_ppp_ip_pool(l2tp)
# check ipv6
if l2tp['client_ipv6_delegate_prefix'] and not l2tp['client_ipv6_pool']:
diff --git a/src/conf_mode/vpn_pptp.py b/src/conf_mode/vpn_pptp.py
index d542f57fe..6243c3ed3 100755
--- a/src/conf_mode/vpn_pptp.py
+++ b/src/conf_mode/vpn_pptp.py
@@ -21,10 +21,14 @@ from copy import deepcopy
from stat import S_IRUSR, S_IWUSR, S_IRGRP
from sys import exit
+
from vyos.config import Config
from vyos.template import render
from vyos.utils.system import get_half_cpus
from vyos.utils.process import call
+from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos import ConfigError
from vyos import airbag
@@ -54,7 +58,7 @@ default_pptp = {
'outside_addr': '',
'dnsv4': [],
'wins': [],
- 'client_ip_pool': '',
+ 'client_ip_pool': {},
'mtu': '1436',
'auth_proto' : ['auth_mschap_v2'],
'ppp_mppe' : 'prefer',
@@ -205,22 +209,24 @@ def get_config(config=None):
conf.set_level(base_path)
if conf.exists(['client-ip-pool']):
- if conf.exists(['client-ip-pool', 'start']) and conf.exists(['client-ip-pool', 'stop']):
- start = conf.return_value(['client-ip-pool', 'start'])
- stop = conf.return_value(['client-ip-pool', 'stop'])
- pptp['client_ip_pool'] = start + '-' + re.search('[0-9]+$', stop).group(0)
+ for pool_name in conf.list_nodes(['client-ip-pool']):
+ pptp['client_ip_pool'][pool_name] = {}
+ pptp['client_ip_pool'][pool_name]['range'] = conf.return_value(['client-ip-pool', pool_name, 'range'])
+ pptp['client_ip_pool'][pool_name]['next_pool'] = conf.return_value(['client-ip-pool', pool_name, 'next-pool'])
+
+ if dict_search('client_ip_pool', pptp):
+ # Multiple named pools require ordered values T5099
+ pptp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', pptp))
+
+ if conf.exists(['default-pool']):
+ pptp['default_pool'] = conf.return_value(['default-pool'])
if conf.exists(['mtu']):
pptp['mtu'] = conf.return_value(['mtu'])
# gateway address
if conf.exists(['gateway-address']):
- pptp['gw_ip'] = conf.return_value(['gateway-address'])
- else:
- # calculate gw-ip-address
- if conf.exists(['client-ip-pool', 'start']):
- # use start ip as gw-ip-address
- pptp['gateway_address'] = conf.return_value(['client-ip-pool', 'start'])
+ pptp['gateway_address'] = conf.return_value(['gateway-address'])
if conf.exists(['authentication', 'require']):
# clear default list content, now populate with actual CLI values
@@ -238,6 +244,7 @@ def get_config(config=None):
if conf.exists(['authentication', 'mppe']):
pptp['ppp_mppe'] = conf.return_value(['authentication', 'mppe'])
+ pptp['server_type'] = 'pptp'
return pptp
@@ -248,21 +255,25 @@ def verify(pptp):
if pptp['auth_mode'] == 'local':
if not pptp['local_users']:
raise ConfigError('PPTP local auth mode requires local users to be configured!')
-
for user in pptp['local_users']:
username = user['name']
if not user['password']:
raise ConfigError(f'Password required for local user "{username}"')
-
elif pptp['auth_mode'] == 'radius':
if len(pptp['radius_server']) == 0:
raise ConfigError('RADIUS authentication requires at least one server')
-
for radius in pptp['radius_server']:
if not radius['key']:
server = radius['server']
raise ConfigError(f'Missing RADIUS secret key for server "{ server }"')
+ if pptp['auth_mode'] == 'local' or pptp['auth_mode'] == 'noauth':
+ if not pptp['client_ip_pool']:
+ raise ConfigError(
+ "PPTP local auth mode requires local client-ip-pool to be configured!")
+
+ verify_accel_ppp_ip_pool(pptp)
+
if len(pptp['dnsv4']) > 2:
raise ConfigError('Not more then two IPv4 DNS name-servers can be configured')
diff --git a/src/conf_mode/vpn_sstp.py b/src/conf_mode/vpn_sstp.py
index e98d8385b..ac053cc76 100755
--- a/src/conf_mode/vpn_sstp.py
+++ b/src/conf_mode/vpn_sstp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-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
@@ -21,13 +21,15 @@ from sys import exit
from vyos.config import Config
from vyos.configdict import get_accel_dict
from vyos.configdict import dict_merge
-from vyos.configverify import verify_accel_ppp_base_service
from vyos.pki import wrap_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.process import call
from vyos.utils.network import check_port_availability
from vyos.utils.dict import dict_search
+from vyos.accel_ppp_util import verify_accel_ppp_base_service
+from vyos.accel_ppp_util import verify_accel_ppp_ip_pool
+from vyos.accel_ppp_util import get_pools_in_order
from vyos.utils.network import is_listen_port_bind_service
from vyos.utils.file import write_file
from vyos import ConfigError
@@ -53,13 +55,17 @@ def get_config(config=None):
# retrieve common dictionary keys
sstp = get_accel_dict(conf, base, sstp_chap_secrets)
+ if dict_search('client_ip_pool', sstp):
+ # Multiple named pools require ordered values T5099
+ sstp['ordered_named_pools'] = get_pools_in_order(dict_search('client_ip_pool', sstp))
if sstp:
sstp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
get_first_key=True,
no_tag_node_value_mangle=True)
-
+ sstp['server_type'] = 'sstp'
return sstp
+
def verify(sstp):
if not sstp:
return None
@@ -75,6 +81,7 @@ def verify(sstp):
if 'client_ip_pool' not in sstp and 'client_ipv6_pool' not in sstp:
raise ConfigError('Client IP subnet required')
+ verify_accel_ppp_ip_pool(sstp)
#
# SSL certificate checks
#
diff --git a/src/migration-scripts/ipoe-server/1-to-2 b/src/migration-scripts/ipoe-server/1-to-2
new file mode 100755
index 000000000..c8cec6835
--- /dev/null
+++ b/src/migration-scripts/ipoe-server/1-to-2
@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+# - changed cli of all named pools
+# - moved gateway-address from pool to global configuration with / netmask
+# gateway can exist without pool if radius is used
+# and Framed-ip-address is transmited
+# - There are several gateway-addresses in ipoe
+# - default-pool by migration.
+# 1. The first pool that contains next-poll.
+# 2. Else, the first pool in the list
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'ipoe-server']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+gateway = ''
+
+#named pool migration
+namedpools_base = pool_base + ['name']
+
+for pool_name in config.list_nodes(namedpools_base):
+ pool_path = namedpools_base + [pool_name]
+ if config.exists(pool_path + ['subnet']):
+ subnet = config.return_value(pool_path + ['subnet'])
+ config.set(pool_base + [pool_name, 'range'], value=subnet)
+ # Get netmask from subnet
+ mask = subnet.split("/")[1]
+ if config.exists(pool_path + ['next-pool']):
+ next_pool = config.return_value(pool_path + ['next-pool'])
+ config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
+ if not default_pool:
+ default_pool = pool_name
+ if config.exists(pool_path + ['gateway-address']) and mask:
+ gateway = f'{config.return_value(pool_path + ["gateway-address"])}/{mask}'
+ config.set(base + ['gateway-address'], value=gateway, replace=False)
+
+if not default_pool and config.list_nodes(namedpools_base):
+ default_pool = config.list_nodes(namedpools_base)[0]
+
+config.delete(namedpools_base)
+
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+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)
diff --git a/src/migration-scripts/l2tp/4-to-5 b/src/migration-scripts/l2tp/4-to-5
new file mode 100755
index 000000000..fe8ab357e
--- /dev/null
+++ b/src/migration-scripts/l2tp/4-to-5
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'l2tp', 'remote-access']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+range_pool_name = 'default-range-pool'
+subnet_pool_name = 'default-subnet-pool'
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ default_pool = subnet_pool_name
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ if default_pool:
+ config.set(pool_base + [range_pool_name, 'next-pool'],
+ value=subnet_pool_name)
+ default_pool = range_pool_name
+
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+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)
diff --git a/src/migration-scripts/pppoe-server/6-to-7 b/src/migration-scripts/pppoe-server/6-to-7
new file mode 100755
index 000000000..8b5482705
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/6-to-7
@@ -0,0 +1,109 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+# - There is only one gateway-address, take the first which is configured
+# - default-pool by migration.
+# 1. If authentication mode = 'local' then it is first named pool.
+# If there are not named pools, namedless pool will be default.
+# 2. If authentication mode = 'radius' then namedless pool will be default
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'pppoe-server']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+default_pool = ''
+range_pool_name = 'default-range-pool'
+subnet_pool_name = 'default-subnet-pool'
+#Default nameless pools migrations
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ default_pool = subnet_pool_name
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ if default_pool:
+ config.set(pool_base + [range_pool_name, 'next-pool'],
+ value=subnet_pool_name)
+ default_pool = range_pool_name
+
+gateway = ''
+if config.exists(base + ['gateway-address']):
+ gateway = config.return_value(base + ['gateway-address'])
+
+#named pool migration
+namedpools_base = pool_base + ['name']
+if config.return_value(base + ['authentication', 'mode']) == 'local':
+ if config.list_nodes(namedpools_base):
+ default_pool = config.list_nodes(namedpools_base)[0]
+
+for pool_name in config.list_nodes(namedpools_base):
+ pool_path = namedpools_base + [pool_name]
+ if config.exists(pool_path + ['subnet']):
+ subnet = config.return_value(pool_path + ['subnet'])
+ config.set(pool_base + [pool_name, 'range'], value=subnet)
+ if config.exists(pool_path + ['next-pool']):
+ next_pool = config.return_value(pool_path + ['next-pool'])
+ config.set(pool_base + [pool_name, 'next-pool'], value=next_pool)
+ if not gateway:
+ if config.exists(pool_path + ['gateway-address']):
+ gateway = config.return_value(pool_path + ['gateway-address'])
+
+config.delete(namedpools_base)
+
+if gateway:
+ config.set(base + ['gateway-address'], value=gateway)
+if default_pool:
+ config.set(base + ['default-pool'], value=default_pool)
+# format as tag node
+config.set_tag(pool_base)
+
+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)
diff --git a/src/migration-scripts/pptp/2-to-3 b/src/migration-scripts/pptp/2-to-3
new file mode 100755
index 000000000..98dc5c2a6
--- /dev/null
+++ b/src/migration-scripts/pptp/2-to-3
@@ -0,0 +1,64 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+# - move all pool to named pools
+# 'start-stop' migrate to namedpool 'default-range-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'pptp', 'remote-access']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+
+range_pool_name = 'default-range-pool'
+
+if config.exists(pool_base + ['start']) and config.exists(pool_base + ['stop']):
+ start_ip = config.return_value(pool_base + ['start'])
+ stop_ip = config.return_value(pool_base + ['stop'])
+ ip_range = f'{start_ip}-{stop_ip}'
+ config.delete(pool_base + ['start'])
+ config.delete(pool_base + ['stop'])
+ config.set(pool_base + [range_pool_name, 'range'], value=ip_range)
+ config.set(base + ['default-pool'], value=range_pool_name)
+# format as tag node
+config.set_tag(pool_base)
+
+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)
diff --git a/src/migration-scripts/sstp/4-to-5 b/src/migration-scripts/sstp/4-to-5
new file mode 100755
index 000000000..0f332e04f
--- /dev/null
+++ b/src/migration-scripts/sstp/4-to-5
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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 <http://www.gnu.org/licenses/>.
+
+# - move all pool to named pools
+# 'subnet' migrate to namedpool 'default-subnet-pool'
+# 'default-subnet-pool' is the next pool for 'default-range-pool'
+
+import os
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['vpn', 'sstp']
+pool_base = base + ['client-ip-pool']
+if not config.exists(base):
+ exit(0)
+
+if not config.exists(pool_base):
+ exit(0)
+
+subnet_pool_name = 'default-subnet-pool'
+if config.exists(pool_base + ['subnet']):
+ subnet = config.return_value(pool_base + ['subnet'])
+ config.delete(pool_base + ['subnet'])
+ config.set(pool_base + [subnet_pool_name, 'range'], value=subnet)
+ config.set(base + ['default-pool'], value=subnet_pool_name)
+# format as tag node
+config.set_tag(pool_base)
+
+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)
diff --git a/src/validators/ipv4-range-mask b/src/validators/ipv4-range-mask
new file mode 100755
index 000000000..7bb4539af
--- /dev/null
+++ b/src/validators/ipv4-range-mask
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+# snippet from https://stackoverflow.com/questions/10768160/ip-address-converter
+ip2dec () {
+ local a b c d ip=$@
+ IFS=. read -r a b c d <<< "$ip"
+ printf '%d\n' "$((a * 256 ** 3 + b * 256 ** 2 + c * 256 + d))"
+}
+
+error_exit() {
+ echo "Error: $1 is not a valid IPv4 address range or these IPs are not under /$2"
+ exit 1
+}
+
+# Check if address range is under the same netmask
+# -m - mask
+# -r - IP range in format x.x.x.x-y.y.y.y
+while getopts m:r: flag
+do
+ case "${flag}" in
+ m) mask=${OPTARG};;
+ r) range=${OPTARG}
+ esac
+done
+if [[ "${range}" =~ "-" ]]&&[[ ! -z ${mask} ]]; then
+ # This only works with real bash (<<<) - split IP addresses into array with
+ # hyphen as delimiter
+ readarray -d - -t strarr <<< ${range}
+
+ ipaddrcheck --is-ipv4-single ${strarr[0]}
+ if [ $? -gt 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ ipaddrcheck --is-ipv4-single ${strarr[1]}
+ if [ $? -gt 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ ${vyos_validators_dir}/numeric --range 0-32 ${mask} > /dev/null
+ if [ $? -ne 0 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ is_in_24=$( grepcidr ${strarr[0]}"/"${mask} <(echo ${strarr[1]}) )
+ if [ -z $is_in_24 ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ start=$(ip2dec ${strarr[0]})
+ stop=$(ip2dec ${strarr[1]})
+ if [ $start -ge $stop ]; then
+ error_exit ${range} ${mask}
+ fi
+
+ exit 0
+fi
+
+error_exit ${range} ${mask}