summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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--data/templates/dns-dynamic/ddclient.conf.j248
-rw-r--r--data/templates/ocserv/ocserv_config.j218
-rw-r--r--debian/vyos-1x.postinst3
-rw-r--r--interface-definitions/dns-dynamic.xml.in226
-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/dns/dynamic-service-host-name-server.xml.i35
-rw-r--r--interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i28
-rw-r--r--interface-definitions/include/dns/dynamic-service-zone.xml.i14
-rw-r--r--interface-definitions/include/version/dns-dynamic-version.xml.i2
-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/policy.xml.in9
-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_policy.py27
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py130
-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/completion/list_ddclient_protocols.sh2
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py92
-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/etc/ipsec.d/vti-up-down21
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/2-to-388
-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-7111
-rwxr-xr-xsrc/migration-scripts/pptp/2-to-364
-rwxr-xr-xsrc/migration-scripts/sstp/4-to-560
-rwxr-xr-xsrc/op_mode/image_installer.py20
-rwxr-xr-xsrc/validators/ddclient-protocol2
-rwxr-xr-xsrc/validators/ipv4-range-mask59
60 files changed, 2150 insertions, 995 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/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2
index 356b8d0d0..30afb9e64 100644
--- a/data/templates/dns-dynamic/ddclient.conf.j2
+++ b/data/templates/dns-dynamic/ddclient.conf.j2
@@ -29,44 +29,28 @@ cache={{ config_file | replace('.conf', '.cache') }}
{# ddclient default (web=dyndns) doesn't support ssl and results in process lockup #}
web=googledomains
{# ddclient default (use=ip) results in confusing warning message in log #}
-use=disabled
+use=no
-{% if address is vyos_defined %}
-{% for address, service_cfg in address.items() %}
-{% if service_cfg.rfc2136 is vyos_defined %}
-{% for name, config in service_cfg.rfc2136.items() %}
-{% if config.description is vyos_defined %}
+{% if name is vyos_defined %}
+{% for service, config in name.items() %}
+{% if config.description is vyos_defined %}
# {{ config.description }}
-{% endif %}
-{% for host in config.host_name if config.host_name is vyos_defined %}
-
-# RFC2136 dynamic DNS configuration for {{ name }}: [{{ config.zone }}, {{ host }}]
-{# Don't append 'new-style' compliant suffix ('usev4', 'usev6', 'ifv4', 'ifv6' etc.)
- to the properties since 'nsupdate' doesn't support that yet. #}
-{{ render_config(host, address, service_cfg.web_options,
- protocol='nsupdate', server=config.server, zone=config.zone,
- password=config.key, ttl=config.ttl) }}
-{% endfor %}
-{% endfor %}
{% endif %}
-{% if service_cfg.service is vyos_defined %}
-{% for name, config in service_cfg.service.items() %}
-{% if config.description is vyos_defined %}
-
-# {{ config.description }}
-{% endif %}
-{% for host in config.host_name if config.host_name is vyos_defined %}
-{% set ip_suffixes = ['v4', 'v6'] if config.ip_version == 'both'
- else [config.ip_version[2:]] %}
+{% for host in config.host_name if config.host_name is vyos_defined %}
+{# ip_suffixes can be either of ['v4'], ['v6'], ['v4', 'v6'] for all protocols except 'nsupdate'
+ ip_suffixes must be [''] for nsupdate since it doesn't support usevX/wantipvX yet #}
+{% set ip_suffixes = ['v4', 'v6'] if config.ip_version == 'both'
+ else ([config.ip_version[2:]] if config.protocol != 'nsupdate'
+ else ['']) %}
+{% set password = config.key if config.protocol == 'nsupdate'
+ else config.password %}
-# Web service dynamic DNS configuration for {{ name }}: [{{ config.protocol }}, {{ host }}]
-{{ render_config(host, address, service_cfg.web_options, ip_suffixes,
+# Web service dynamic DNS configuration for {{ service }}: [{{ config.protocol }}, {{ host }}]
+{{ render_config(host, config.address, config.web_options, ip_suffixes,
protocol=config.protocol, server=config.server, zone=config.zone,
- login=config.username, password=config.password, ttl=config.ttl,
+ login=config.username, password=password, ttl=config.ttl,
min_interval=config.wait_time, max_interval=config.expiry_time) }}
-{% endfor %}
-{% endfor %}
-{% endif %}
+{% endfor %}
{% endfor %}
{% endif %}
diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index 1401b8b26..80ba357bc 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -119,4 +119,20 @@ split-dns = {{ tmp }}
{% for grp in authentication.group %}
select-group = {{ grp }}
{% endfor %}
-{% endif %} \ No newline at end of file
+{% endif %}
+
+
+# HTTP security headers
+included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains
+included-http-headers = X-Frame-Options: deny
+included-http-headers = X-Content-Type-Options: nosniff
+included-http-headers = Content-Security-Policy: default-src ´none´
+included-http-headers = X-Permitted-Cross-Domain-Policies: none
+included-http-headers = Referrer-Policy: no-referrer
+included-http-headers = Clear-Site-Data: "cache","cookies","storage"
+included-http-headers = Cross-Origin-Embedder-Policy: require-corp
+included-http-headers = Cross-Origin-Opener-Policy: same-origin
+included-http-headers = Cross-Origin-Resource-Policy: same-origin
+included-http-headers = X-XSS-Protection: 0
+included-http-headers = Pragma: no-cache
+included-http-headers = Cache-control: no-store, no-cache
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index 22b50ce2a..64c60a780 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -172,7 +172,7 @@ fi
DELETE="/etc/logrotate.d/conntrackd.distrib /etc/init.d/conntrackd /etc/default/conntrackd
/etc/default/pmacctd /etc/pmacct
/etc/networks_list /etc/networks_whitelist /etc/fastnetmon.conf
- /etc/ntp.conf /etc/default/ssh
+ /etc/ntp.conf /etc/default/ssh /etc/avahi/avahi-daemon.conf /etc/avahi/hosts
/etc/powerdns /etc/default/pdns-recursor
/etc/ppp/ip-up.d/0000usepeerdns /etc/ppp/ip-down.d/0000usepeerdns"
for tmp in $DELETE; do
@@ -200,4 +200,3 @@ systemctl enable vyos-config-cloud-init.service
# Update XML cache
python3 /usr/lib/python3/dist-packages/vyos/xml_ref/update_cache.py
-
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index 32c5af9b6..f089f0e52 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -12,27 +12,48 @@
<help>Dynamic DNS</help>
</properties>
<children>
- <tagNode name="address">
+ <tagNode name="name">
<properties>
- <help>Obtain IP address to send Dynamic DNS update for</help>
+ <help>Dynamic DNS configuration</help>
<valueHelp>
<format>txt</format>
- <description>Use interface to obtain the IP address</description>
+ <description>Dynamic DNS service name</description>
</valueHelp>
- <valueHelp>
- <format>web</format>
- <description>Use HTTP(S) web request to obtain the IP address</description>
- </valueHelp>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- <list>web</list>
- </completionHelp>
- <constraint>
- #include <include/constraint/interface-name.xml.i>
- <regex>web</regex>
- </constraint>
</properties>
<children>
+ #include <include/generic-description.xml.i>
+ <leafNode name="protocol">
+ <properties>
+ <help>ddclient protocol used for Dynamic DNS service</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_ddclient_protocols.sh</script>
+ </completionHelp>
+ <constraint>
+ <validator name="ddclient-protocol"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="address">
+ <properties>
+ <help>Obtain IP address to send Dynamic DNS update for</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Use interface to obtain the IP address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>web</format>
+ <description>Use HTTP(S) web request to obtain the IP address</description>
+ </valueHelp>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <list>web</list>
+ </completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ <regex>web</regex>
+ </constraint>
+ </properties>
+ </leafNode>
<node name="web-options">
<properties>
<help>Options when using HTTP(S) web request to obtain the IP address</help>
@@ -50,88 +71,117 @@
</leafNode>
</children>
</node>
- <tagNode name="rfc2136">
+ <leafNode name="ip-version">
<properties>
- <help>RFC2136 nsupdate configuration</help>
+ <help>IP address version to use</help>
<valueHelp>
- <format>txt</format>
- <description>RFC2136 nsupdate service name</description>
+ <format>_ipv4</format>
+ <description>Use only IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>_ipv6</format>
+ <description>Use only IPv6 address</description>
</valueHelp>
+ <valueHelp>
+ <format>both</format>
+ <description>Use both IPv4 and IPv6 address</description>
+ </valueHelp>
+ <completionHelp>
+ <list>ipv4 ipv6 both</list>
+ </completionHelp>
+ <constraint>
+ <regex>(ipv[46]|both)</regex>
+ </constraint>
+ <constraintErrorMessage>IP Version must be literal 'ipv4', 'ipv6' or 'both'</constraintErrorMessage>
</properties>
- <children>
- #include <include/generic-description.xml.i>
- #include <include/dns/dynamic-service-host-name-server.xml.i>
- #include <include/dns/dynamic-service-wait-expiry-time.xml.i>
- <leafNode name="key">
- <properties>
- <help>File containing the TSIG secret key shared with remote DNS server</help>
- <valueHelp>
- <format>filename</format>
- <description>File in /config/auth directory</description>
- </valueHelp>
- <constraint>
- <validator name="file-path" argument="--strict --parent-dir /config/auth"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/dns/time-to-live.xml.i>
- #include <include/dns/dynamic-service-zone.xml.i>
- </children>
- </tagNode>
- <tagNode name="service">
+ <defaultValue>ipv4</defaultValue>
+ </leafNode>
+ <leafNode name="host-name">
+ <properties>
+ <help>Hostname to register with Dynamic DNS service</help>
+ <constraint>
+ #include <include/constraint/host-name.xml.i>
+ <regex>(\@|\*)[-.A-Za-z0-9]*</regex>
+ </constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*'</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="server">
<properties>
- <help>Dynamic DNS configuration</help>
+ <help>Remote Dynamic DNS server to send updates to</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address of the remote server</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of the remote server</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Fully qualified domain name of the remote server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ <constraintErrorMessage>Remote server must be IP address or fully qualified domain name</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="zone">
+ <properties>
+ <help>DNS zone to be updated</help>
<valueHelp>
<format>txt</format>
- <description>Dynamic DNS service name</description>
+ <description>Name of DNS zone</description>
</valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
- <children>
- #include <include/generic-description.xml.i>
- #include <include/dns/dynamic-service-host-name-server.xml.i>
- #include <include/dns/dynamic-service-wait-expiry-time.xml.i>
- #include <include/generic-username.xml.i>
- #include <include/generic-password.xml.i>
- #include <include/dns/time-to-live.xml.i>
- <leafNode name="protocol">
- <properties>
- <help>ddclient protocol used for Dynamic DNS service</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_ddclient_protocols.sh</script>
- </completionHelp>
- <constraint>
- <validator name="ddclient-protocol"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/dns/dynamic-service-zone.xml.i>
- <leafNode name="ip-version">
- <properties>
- <help>IP address version to use</help>
- <valueHelp>
- <format>_ipv4</format>
- <description>Use only IPv4 address</description>
- </valueHelp>
- <valueHelp>
- <format>_ipv6</format>
- <description>Use only IPv6 address</description>
- </valueHelp>
- <valueHelp>
- <format>both</format>
- <description>Use both IPv4 and IPv6 address</description>
- </valueHelp>
- <completionHelp>
- <list>ipv4 ipv6 both</list>
- </completionHelp>
- <constraint>
- <regex>(ipv[46]|both)</regex>
- </constraint>
- <constraintErrorMessage>IP Version must be literal 'ipv4', 'ipv6' or 'both'</constraintErrorMessage>
- </properties>
- <defaultValue>ipv4</defaultValue>
- </leafNode>
- </children>
- </tagNode>
+ </leafNode>
+ #include <include/generic-username.xml.i>
+ #include <include/generic-password.xml.i>
+ <leafNode name="key">
+ <properties>
+ <help>File containing TSIG authentication key for RFC2136 nsupdate on remote DNS server</help>
+ <valueHelp>
+ <format>filename</format>
+ <description>File in /config/auth directory</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--strict --parent-dir /config/auth"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/dns/time-to-live.xml.i>
+ <leafNode name="wait-time">
+ <properties>
+ <help>Time in seconds to wait between update attempts</help>
+ <valueHelp>
+ <format>u32:60-86400</format>
+ <description>Time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 60-86400"/>
+ </constraint>
+ <constraintErrorMessage>Wait time must be between 60 and 86400 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="expiry-time">
+ <properties>
+ <help>Time in seconds for the hostname to be marked expired in cache</help>
+ <valueHelp>
+ <format>u32:300-2160000</format>
+ <description>Time in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 300-2160000"/>
+ </constraint>
+ <constraintErrorMessage>Expiry time must be between 300 and 2160000 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<leafNode name="interval">
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/dns/dynamic-service-host-name-server.xml.i b/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i
deleted file mode 100644
index 9dd14f97c..000000000
--- a/interface-definitions/include/dns/dynamic-service-host-name-server.xml.i
+++ /dev/null
@@ -1,35 +0,0 @@
-<!-- include start from dns/dynamic-service-host-name-server.xml.i -->
-<leafNode name="host-name">
- <properties>
- <help>Hostname to register with Dynamic DNS service</help>
- <constraint>
- #include <include/constraint/host-name.xml.i>
- <regex>(\@|\*)[-.A-Za-z0-9]*</regex>
- </constraint>
- <constraintErrorMessage>Host-name must be alphanumeric, can contain hyphens and can be prefixed with '@' or '*'</constraintErrorMessage>
- <multi/>
- </properties>
-</leafNode>
-<leafNode name="server">
- <properties>
- <help>Remote Dynamic DNS server to send updates to</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address of the remote server</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address of the remote server</description>
- </valueHelp>
- <valueHelp>
- <format>hostname</format>
- <description>Fully qualified domain name of the remote server</description>
- </valueHelp>
- <constraint>
- <validator name="ip-address"/>
- <validator name="fqdn"/>
- </constraint>
- <constraintErrorMessage>Remote server must be IP address or fully qualified domain name</constraintErrorMessage>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i b/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i
deleted file mode 100644
index 866690cbe..000000000
--- a/interface-definitions/include/dns/dynamic-service-wait-expiry-time.xml.i
+++ /dev/null
@@ -1,28 +0,0 @@
-<!-- include start from dns/dynamic-service-wait-expiry-time.xml.i -->
-<leafNode name="wait-time">
- <properties>
- <help>Time in seconds to wait between update attempts</help>
- <valueHelp>
- <format>u32:60-86400</format>
- <description>Time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 60-86400"/>
- </constraint>
- <constraintErrorMessage>Wait time must be between 60 and 86400 seconds</constraintErrorMessage>
- </properties>
-</leafNode>
-<leafNode name="expiry-time">
- <properties>
- <help>Time in seconds for the hostname to be marked expired in cache</help>
- <valueHelp>
- <format>u32:300-2160000</format>
- <description>Time in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 300-2160000"/>
- </constraint>
- <constraintErrorMessage>Expiry time must be between 300 and 2160000 seconds</constraintErrorMessage>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/dns/dynamic-service-zone.xml.i b/interface-definitions/include/dns/dynamic-service-zone.xml.i
deleted file mode 100644
index 0cc00468f..000000000
--- a/interface-definitions/include/dns/dynamic-service-zone.xml.i
+++ /dev/null
@@ -1,14 +0,0 @@
-<!-- include start from dns/dynamic-service-zone.xml.i -->
-<leafNode name="zone">
- <properties>
- <help>DNS zone to be updated</help>
- <valueHelp>
- <format>txt</format>
- <description>Name of DNS zone</description>
- </valueHelp>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- </properties>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/version/dns-dynamic-version.xml.i b/interface-definitions/include/version/dns-dynamic-version.xml.i
index 7bdb90a35..773a6ab51 100644
--- a/interface-definitions/include/version/dns-dynamic-version.xml.i
+++ b/interface-definitions/include/version/dns-dynamic-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dns-dynamic-version.xml.i -->
-<syntaxVersion component='dns-dynamic' version='2'></syntaxVersion>
+<syntaxVersion component='dns-dynamic' version='3'></syntaxVersion>
<!-- 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/policy.xml.in b/interface-definitions/policy.xml.in
index c470cfdb3..0d2ed9746 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1446,9 +1446,18 @@
<format>u32:0-4294967295</format>
<description>Metric value</description>
</valueHelp>
+ <valueHelp>
+ <format>&lt;+/-rtt&gt;</format>
+ <description>Add or subtract round trip time</description>
+ </valueHelp>
+ <valueHelp>
+ <format>&lt;rtt&gt;</format>
+ <description>Round trip time</description>
+ </valueHelp>
<constraint>
<validator name="numeric" argument="--relative --"/>
<validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>^[+|-]?rtt$</regex>
</constraint>
</properties>
</leafNode>
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_policy.py b/smoketest/scripts/cli/test_policy.py
index 51a33f978..c21d8af4e 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1107,6 +1107,33 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'metric' : '-20',
},
},
+ '30': {
+ 'action': 'permit',
+ 'match': {
+ 'ip-nexthop-addr': ipv4_nexthop_address,
+ },
+ 'set': {
+ 'metric': 'rtt',
+ },
+ },
+ '40': {
+ 'action': 'permit',
+ 'match': {
+ 'ip-nexthop-addr': ipv4_nexthop_address,
+ },
+ 'set': {
+ 'metric': '+rtt',
+ },
+ },
+ '50': {
+ 'action': 'permit',
+ 'match': {
+ 'ip-nexthop-addr': ipv4_nexthop_address,
+ },
+ 'set': {
+ 'metric': '-rtt',
+ },
+ },
},
},
}
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index fe213a8ae..cb3d90593 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -32,6 +32,7 @@ DDCLIENT_PID = '/run/ddclient/ddclient.pid'
DDCLIENT_PNAME = 'ddclient'
base_path = ['service', 'dns', 'dynamic']
+name_path = base_path + ['name']
server = 'ddns.vyos.io'
hostname = 'test.ddns.vyos.io'
zone = 'vyos.io'
@@ -58,38 +59,38 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# IPv4 standard DDNS service configuration
def test_01_dyndns_service_standard(self):
- svc_path = ['address', interface, 'service']
services = {'cloudflare': {'protocol': 'cloudflare'},
'freedns': {'protocol': 'freedns', 'username': username},
'zoneedit': {'protocol': 'zoneedit1', 'username': username}}
for svc, details in services.items():
- self.cli_set(base_path + svc_path + [svc, 'host-name', hostname])
- self.cli_set(base_path + svc_path + [svc, 'password', password])
- self.cli_set(base_path + svc_path + [svc, 'zone', zone])
- self.cli_set(base_path + svc_path + [svc, 'ttl', ttl])
+ self.cli_set(name_path + [svc, 'address', interface])
+ self.cli_set(name_path + [svc, 'host-name', hostname])
+ self.cli_set(name_path + [svc, 'password', password])
+ self.cli_set(name_path + [svc, 'zone', zone])
+ self.cli_set(name_path + [svc, 'ttl', ttl])
for opt, value in details.items():
- self.cli_set(base_path + svc_path + [svc, opt, value])
+ self.cli_set(name_path + [svc, opt, value])
# 'zone' option is supported and required by 'cloudfare', but not 'freedns' and 'zoneedit'
- self.cli_set(base_path + svc_path + [svc, 'zone', zone])
+ self.cli_set(name_path + [svc, 'zone', zone])
if details['protocol'] == 'cloudflare':
pass
else:
# exception is raised for unsupported ones
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_delete(base_path + svc_path + [svc, 'zone'])
+ self.cli_delete(name_path + [svc, 'zone'])
# 'ttl' option is supported by 'cloudfare', but not 'freedns' and 'zoneedit'
- self.cli_set(base_path + svc_path + [svc, 'ttl', ttl])
+ self.cli_set(name_path + [svc, 'ttl', ttl])
if details['protocol'] == 'cloudflare':
pass
else:
# exception is raised for unsupported ones
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_delete(base_path + svc_path + [svc, 'ttl'])
+ self.cli_delete(name_path + [svc, 'ttl'])
# commit changes
self.cli_commit()
@@ -113,7 +114,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# IPv6 only DDNS service configuration
def test_02_dyndns_service_ipv6(self):
interval = '60'
- svc_path = ['address', interface, 'service', 'dynv6']
+ svc_path = name_path + ['dynv6']
proto = 'dyndns2'
ip_version = 'ipv6'
wait_time = '600'
@@ -121,19 +122,20 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
expiry_time_bad = '360'
self.cli_set(base_path + ['interval', interval])
- self.cli_set(base_path + svc_path + ['ip-version', ip_version])
- self.cli_set(base_path + svc_path + ['protocol', proto])
- self.cli_set(base_path + svc_path + ['server', server])
- self.cli_set(base_path + svc_path + ['username', username])
- self.cli_set(base_path + svc_path + ['password', password])
- self.cli_set(base_path + svc_path + ['host-name', hostname])
- self.cli_set(base_path + svc_path + ['wait-time', wait_time])
+ self.cli_set(svc_path + ['address', interface])
+ self.cli_set(svc_path + ['ip-version', ip_version])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['server', server])
+ self.cli_set(svc_path + ['username', username])
+ self.cli_set(svc_path + ['password', password])
+ self.cli_set(svc_path + ['host-name', hostname])
+ self.cli_set(svc_path + ['wait-time', wait_time])
# expiry-time must be greater than wait-time, exception is raised otherwise
- self.cli_set(base_path + svc_path + ['expiry-time', expiry_time_bad])
with self.assertRaises(ConfigSessionError):
+ self.cli_set(svc_path + ['expiry-time', expiry_time_bad])
self.cli_commit()
- self.cli_set(base_path + svc_path + ['expiry-time', expiry_time_good])
+ self.cli_set(svc_path + ['expiry-time', expiry_time_good])
# commit changes
self.cli_commit()
@@ -152,25 +154,25 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# IPv4+IPv6 dual DDNS service configuration
def test_03_dyndns_service_dual_stack(self):
- svc_path = ['address', interface, 'service']
services = {'cloudflare': {'protocol': 'cloudflare', 'zone': zone},
'freedns': {'protocol': 'freedns', 'username': username},
'google': {'protocol': 'googledomains', 'username': username}}
ip_version = 'both'
for name, details in services.items():
- self.cli_set(base_path + svc_path + [name, 'host-name', hostname])
- self.cli_set(base_path + svc_path + [name, 'password', password])
+ self.cli_set(name_path + [name, 'address', interface])
+ self.cli_set(name_path + [name, 'host-name', hostname])
+ self.cli_set(name_path + [name, 'password', password])
for opt, value in details.items():
- self.cli_set(base_path + svc_path + [name, opt, value])
+ self.cli_set(name_path + [name, opt, value])
# Dual stack is supported by 'cloudfare' and 'freedns' but not 'googledomains'
# exception is raised for unsupported ones
- self.cli_set(base_path + svc_path + [name, 'ip-version', ip_version])
+ self.cli_set(name_path + [name, 'ip-version', ip_version])
if details['protocol'] not in ['cloudflare', 'freedns']:
with self.assertRaises(ConfigSessionError):
self.cli_commit()
- self.cli_delete(base_path + svc_path + [name, 'ip-version'])
+ self.cli_delete(name_path + [name, 'ip-version'])
# commit changes
self.cli_commit()
@@ -197,16 +199,19 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
def test_04_dyndns_rfc2136(self):
# Check if DDNS service can be configured and runs
- svc_path = ['address', interface, 'rfc2136', 'vyos']
+ svc_path = name_path + ['vyos']
+ proto = 'nsupdate'
with tempfile.NamedTemporaryFile(prefix='/config/auth/') as key_file:
key_file.write(b'S3cretKey')
- self.cli_set(base_path + svc_path + ['server', server])
- self.cli_set(base_path + svc_path + ['zone', zone])
- self.cli_set(base_path + svc_path + ['key', key_file.name])
- self.cli_set(base_path + svc_path + ['ttl', ttl])
- self.cli_set(base_path + svc_path + ['host-name', hostname])
+ self.cli_set(svc_path + ['address', interface])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['server', server])
+ self.cli_set(svc_path + ['zone', zone])
+ self.cli_set(svc_path + ['key', key_file.name])
+ self.cli_set(svc_path + ['ttl', ttl])
+ self.cli_set(svc_path + ['host-name', hostname])
# commit changes
self.cli_commit()
@@ -215,7 +220,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}')
self.assertIn(f'use=if', ddclient_conf)
self.assertIn(f'if={interface}', ddclient_conf)
- self.assertIn(f'protocol=nsupdate', ddclient_conf)
+ self.assertIn(f'protocol={proto}', ddclient_conf)
self.assertIn(f'server={server}', ddclient_conf)
self.assertIn(f'zone={zone}', ddclient_conf)
self.assertIn(f'password=\'{key_file.name}\'', ddclient_conf)
@@ -223,16 +228,17 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
def test_05_dyndns_hostname(self):
# Check if DDNS service can be configured and runs
- svc_path = ['address', interface, 'service', 'namecheap']
+ svc_path = name_path + ['namecheap']
proto = 'namecheap'
hostnames = ['@', 'www', hostname, f'@.{hostname}']
for name in hostnames:
- self.cli_set(base_path + svc_path + ['protocol', proto])
- self.cli_set(base_path + svc_path + ['server', server])
- self.cli_set(base_path + svc_path + ['username', username])
- self.cli_set(base_path + svc_path + ['password', password])
- self.cli_set(base_path + svc_path + ['host-name', name])
+ self.cli_set(svc_path + ['address', interface])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['server', server])
+ self.cli_set(svc_path + ['username', username])
+ self.cli_set(svc_path + ['password', password])
+ self.cli_set(svc_path + ['host-name', name])
# commit changes
self.cli_commit()
@@ -247,42 +253,32 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
def test_06_dyndns_web_options(self):
# Check if DDNS service can be configured and runs
- base_path_iface = base_path + ['address', interface]
- base_path_web = base_path + ['address', 'web']
- svc_path_iface = base_path_iface + ['service', 'cloudflare']
- svc_path_web = base_path_web + ['service', 'cloudflare']
+ svc_path = name_path + ['cloudflare']
proto = 'cloudflare'
web_url_good = 'https://ifconfig.me/ip'
web_url_bad = 'http:/ifconfig.me/ip'
- self.cli_set(svc_path_iface + ['protocol', proto])
- self.cli_set(svc_path_iface + ['zone', zone])
- self.cli_set(svc_path_iface + ['password', password])
- self.cli_set(svc_path_iface + ['host-name', hostname])
- self.cli_set(base_path_iface + ['web-options', 'url', web_url_good])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['zone', zone])
+ self.cli_set(svc_path + ['password', password])
+ self.cli_set(svc_path + ['host-name', hostname])
+ self.cli_set(svc_path + ['web-options', 'url', web_url_good])
# web-options is supported only with web service based address lookup
# exception is raised for interface based address lookup
with self.assertRaises(ConfigSessionError):
+ self.cli_set(svc_path + ['address', interface])
self.cli_commit()
- self.cli_delete(base_path_iface + ['web-options'])
+ self.cli_set(svc_path + ['address', 'web'])
# commit changes
self.cli_commit()
- # web-options is supported with web service based address lookup
- # this should work, but clear interface based config first
- self.cli_delete(base_path_iface)
- self.cli_set(svc_path_web + ['protocol', proto])
- self.cli_set(svc_path_web + ['zone', zone])
- self.cli_set(svc_path_web + ['password', password])
- self.cli_set(svc_path_web + ['host-name', hostname])
-
# web-options must be a valid URL
- with self.assertRaises(ConfigSessionError) as cm:
- self.cli_set(base_path_web + ['web-options', 'url', web_url_bad])
- self.assertIn(f'"{web_url_bad.removeprefix("http:")}" is not a valid URI', str(cm.exception))
- self.cli_set(base_path_web + ['web-options', 'url', web_url_good])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_set(svc_path + ['web-options', 'url', web_url_bad])
+ self.cli_commit()
+ self.cli_set(svc_path + ['web-options', 'url', web_url_good])
# commit changes
self.cli_commit()
@@ -300,15 +296,17 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# Table number randomized, but should be within range 100-65535
vrf_table = "".join(random.choices(string.digits, k=4))
vrf_name = f'vyos-test-{vrf_table}'
- svc_path = ['address', interface, 'service', 'cloudflare']
+ svc_path = name_path + ['cloudflare']
+ proto = 'cloudflare'
self.cli_set(['vrf', 'name', vrf_name, 'table', vrf_table])
self.cli_set(base_path + ['vrf', vrf_name])
- self.cli_set(base_path + svc_path + ['protocol', 'cloudflare'])
- self.cli_set(base_path + svc_path + ['host-name', hostname])
- self.cli_set(base_path + svc_path + ['zone', zone])
- self.cli_set(base_path + svc_path + ['password', password])
+ self.cli_set(svc_path + ['address', interface])
+ self.cli_set(svc_path + ['protocol', proto])
+ self.cli_set(svc_path + ['host-name', hostname])
+ self.cli_set(svc_path + ['zone', zone])
+ self.cli_set(svc_path + ['password', password])
# commit changes
self.cli_commit()
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/completion/list_ddclient_protocols.sh b/src/completion/list_ddclient_protocols.sh
index c8855b5d1..634981660 100755
--- a/src/completion/list_ddclient_protocols.sh
+++ b/src/completion/list_ddclient_protocols.sh
@@ -14,4 +14,4 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-echo -n $(ddclient -list-protocols | grep -vE 'nsupdate|cloudns|porkbun')
+echo -n $(ddclient -list-protocols | grep -vE 'cloudns|porkbun')
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index 2bccaee0f..3ddc8e7fd 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -30,16 +30,18 @@ config_file = r'/run/ddclient/ddclient.conf'
systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf'
# Protocols that require zone
-zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi', 'nfsn']
+zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi',
+ 'nfsn', 'nsupdate']
zone_supported = zone_necessary + ['dnsexit2', 'zoneedit1']
# Protocols that do not require username
username_unnecessary = ['1984', 'cloudflare', 'cloudns', 'digitalocean', 'dnsexit2',
'duckdns', 'freemyip', 'hetzner', 'keysystems', 'njalla',
- 'regfishde']
+ 'nsupdate', 'regfishde']
# Protocols that support TTL
-ttl_supported = ['cloudflare', 'dnsexit2', 'gandi', 'hetzner', 'godaddy', 'nfsn']
+ttl_supported = ['cloudflare', 'dnsexit2', 'gandi', 'hetzner', 'godaddy', 'nfsn',
+ 'nsupdate']
# Protocols that support both IPv4 and IPv6
dualstack_supported = ['cloudflare', 'digitalocean', 'dnsexit2', 'duckdns',
@@ -70,63 +72,65 @@ def get_config(config=None):
def verify(dyndns):
# bail out early - looks like removal from running config
- if not dyndns or 'address' not in dyndns:
+ if not dyndns or 'name' not in dyndns:
return None
- for address in dyndns['address']:
- # If dyndns address is an interface, ensure it exists
- if address != 'web':
- verify_interface_exists(address)
+ # Dynamic DNS service provider - configuration validation
+ for service, config in dyndns['name'].items():
- # RFC2136 - configuration validation
- if 'rfc2136' in dyndns['address'][address]:
- for config in dyndns['address'][address]['rfc2136'].values():
- for field in ['host_name', 'zone', 'server', 'key']:
- if field not in config:
- raise ConfigError(f'"{field.replace("_", "-")}" is required for RFC2136 '
- f'based Dynamic DNS service on "{address}"')
+ error_msg_req = f'is required for Dynamic DNS service "{service}"'
+ error_msg_uns = f'is not supported for Dynamic DNS service "{service}"'
- # Dynamic DNS service provider - configuration validation
- if 'web_options' in dyndns['address'][address] and address != 'web':
- raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address')
+ for field in ['protocol', 'address', 'host_name']:
+ if field not in config:
+ raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}')
- # Dynamic DNS service provider - configuration validation
- if 'service' in dyndns['address'][address]:
- for service, config in dyndns['address'][address]['service'].items():
- error_msg_req = f'is required for Dynamic DNS service "{service}" on "{address}"'
- error_msg_uns = f'is not supported for Dynamic DNS service "{service}" on "{address}" with protocol "{config["protocol"]}"'
+ # If dyndns address is an interface, ensure that it exists
+ # and that web-options are not set
+ if config['address'] != 'web':
+ verify_interface_exists(config['address'])
+ if 'web_options' in config:
+ raise ConfigError(f'"web-options" is applicable only when using HTTP(S) web request to obtain the IP address')
- for field in ['host_name', 'password', 'protocol']:
- if field not in config:
- raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}')
+ # RFC2136 uses 'key' instead of 'password'
+ if config['protocol'] != 'nsupdate' and 'password' not in config:
+ raise ConfigError(f'"password" {error_msg_req}')
- if config['protocol'] in zone_necessary and 'zone' not in config:
- raise ConfigError(f'"zone" {error_msg_req} with protocol "{config["protocol"]}"')
+ # Other RFC2136 specific configuration validation
+ if config['protocol'] == 'nsupdate':
+ if 'password' in config:
+ raise ConfigError(f'"password" {error_msg_uns} with protocol "{config["protocol"]}"')
+ for field in ['server', 'key']:
+ if field not in config:
+ raise ConfigError(f'"{field}" {error_msg_req} with protocol "{config["protocol"]}"')
- if config['protocol'] not in zone_supported and 'zone' in config:
- raise ConfigError(f'"zone" {error_msg_uns}')
+ if config['protocol'] in zone_necessary and 'zone' not in config:
+ raise ConfigError(f'"zone" {error_msg_req} with protocol "{config["protocol"]}"')
- if config['protocol'] not in username_unnecessary and 'username' not in config:
- raise ConfigError(f'"username" {error_msg_req} with protocol "{config["protocol"]}"')
+ if config['protocol'] not in zone_supported and 'zone' in config:
+ raise ConfigError(f'"zone" {error_msg_uns} with protocol "{config["protocol"]}"')
- if config['protocol'] not in ttl_supported and 'ttl' in config:
- raise ConfigError(f'"ttl" {error_msg_uns}')
+ if config['protocol'] not in username_unnecessary and 'username' not in config:
+ raise ConfigError(f'"username" {error_msg_req} with protocol "{config["protocol"]}"')
- if config['ip_version'] == 'both':
- if config['protocol'] not in dualstack_supported:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns}')
- # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org)
- if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers:
- raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}"')
+ if config['protocol'] not in ttl_supported and 'ttl' in config:
+ raise ConfigError(f'"ttl" {error_msg_uns} with protocol "{config["protocol"]}"')
- if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']):
- raise ConfigError(f'"expiry-time" must be greater than "wait-time"')
+ if config['ip_version'] == 'both':
+ if config['protocol'] not in dualstack_supported:
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} with protocol "{config["protocol"]}"')
+ # dyndns2 protocol in ddclient honors dual stack only for dyn.com (dyndns.org)
+ if config['protocol'] == 'dyndns2' and 'server' in config and config['server'] not in dyndns_dualstack_servers:
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} for "{config["server"]}" with protocol "{config["protocol"]}"')
+
+ if {'wait_time', 'expiry_time'} <= config.keys() and int(config['expiry_time']) < int(config['wait_time']):
+ raise ConfigError(f'"expiry-time" must be greater than "wait-time" for Dynamic DNS service "{service}"')
return None
def generate(dyndns):
# bail out early - looks like removal from running config
- if not dyndns or 'address' not in dyndns:
+ if not dyndns or 'name' not in dyndns:
return None
render(config_file, 'dns-dynamic/ddclient.conf.j2', dyndns, permission=0o600)
@@ -139,7 +143,7 @@ def apply(dyndns):
call('systemctl daemon-reload')
# bail out early - looks like removal from running config
- if not dyndns or 'address' not in dyndns:
+ if not dyndns or 'name' not in dyndns:
call(f'systemctl stop {systemd_service}')
if os.path.exists(config_file):
os.unlink(config_file)
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/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down
index 9eb6fac48..441b316c2 100755
--- a/src/etc/ipsec.d/vti-up-down
+++ b/src/etc/ipsec.d/vti-up-down
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -13,8 +13,9 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-## Script called up strongswan to bring the vti interface up/down based on the state of the IPSec tunnel.
-## Called as vti_up_down vti_intf_name
+
+# Script called up strongswan to bring the VTI interface up/down based on
+# the state of the IPSec tunnel. Called as vti_up_down vti_intf_name
import os
import sys
@@ -25,9 +26,10 @@ from syslog import LOG_PID
from syslog import LOG_INFO
from vyos.configquery import ConfigTreeQuery
+from vyos.configdict import get_interface_dict
+from vyos.ifconfig import VTIIf
from vyos.utils.process import call
from vyos.utils.network import get_interface_config
-from vyos.utils.network import get_interface_address
if __name__ == '__main__':
verb = os.getenv('PLUTO_VERB')
@@ -48,14 +50,13 @@ if __name__ == '__main__':
vti_link_up = (vti_link['operstate'] != 'DOWN' if 'operstate' in vti_link else False)
- config = ConfigTreeQuery()
- vti_dict = config.get_config_dict(['interfaces', 'vti', interface],
- get_first_key=True)
-
if verb in ['up-client', 'up-host']:
if not vti_link_up:
- if 'disable' not in vti_dict:
- call(f'sudo ip link set {interface} up')
+ conf = ConfigTreeQuery()
+ _, vti = get_interface_dict(conf.config, ['interfaces', 'vti'], interface)
+ if 'disable' not in vti:
+ tmp = VTIIf(interface)
+ tmp.update(vti)
else:
syslog(f'Interface {interface} is admin down ...')
elif verb in ['down-client', 'down-host']:
diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3
new file mode 100755
index 000000000..187c2a895
--- /dev/null
+++ b/src/migration-scripts/dns-dynamic/2-to-3
@@ -0,0 +1,88 @@
+#!/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/>.
+
+# T5791:
+# - migrate "service dns dynamic address web web-options ..."
+# to "service dns dynamic name <service> address web ..." (per service)
+# - migrate "service dns dynamic address <address> rfc2136 <service> ..."
+# to "service dns dynamic name <service> address <interface> protocol 'nsupdate'"
+# - migrate "service dns dynamic address <interface> service <service> ..."
+# to "service dns dynamic name <service> address <interface> ..."
+
+import sys
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+base_path = ['service', 'dns', 'dynamic']
+address_path = base_path + ['address']
+name_path = base_path + ['name']
+
+if not config.exists(address_path):
+ # Nothing to do
+ sys.exit(0)
+
+# config.copy does not recursively create a path, so initialize the name path as tagged node
+if not config.exists(name_path):
+ config.set(name_path)
+ config.set_tag(name_path)
+
+for address in config.list_nodes(address_path):
+
+ address_path_tag = address_path + [address]
+
+ # Move web-option as a configuration in each service instead of top level web-option
+ if config.exists(address_path_tag + ['web-options']) and address == 'web':
+ for svc_type in ['service', 'rfc2136']:
+ if config.exists(address_path_tag + [svc_type]):
+ for svc_cfg in config.list_nodes(address_path_tag + [svc_type]):
+ config.copy(address_path_tag + ['web-options'],
+ address_path_tag + [svc_type, svc_cfg, 'web-options'])
+ config.delete(address_path_tag + ['web-options'])
+
+ for svc_type in ['service', 'rfc2136']:
+ if config.exists(address_path_tag + [svc_type]):
+ # Move RFC2136 as service configuration, rename to avoid name conflict and set protocol to 'nsupdate'
+ if svc_type == 'rfc2136':
+ for rfc_cfg_old in config.list_nodes(address_path_tag + ['rfc2136']):
+ rfc_cfg_new = f'{rfc_cfg_old}-rfc2136'
+ config.rename(address_path_tag + ['rfc2136', rfc_cfg_old], rfc_cfg_new)
+ config.set(address_path_tag + ['rfc2136', rfc_cfg_new, 'protocol'], 'nsupdate')
+
+ # Add address as config value in each service before moving the service path
+ # And then copy the services from 'address <interface> service <service>' to 'name <service>'
+ for svc_cfg in config.list_nodes(address_path_tag + [svc_type]):
+ config.set(address_path_tag + [svc_type, svc_cfg, 'address'], address)
+ config.copy(address_path_tag + [svc_type, svc_cfg], name_path + [svc_cfg])
+
+# Finally cleanup the old address path
+config.delete(address_path)
+
+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))
+ sys.exit(1)
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..34996d8fe
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/6-to-7
@@ -0,0 +1,111 @@
+#!/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.exists(namedpools_base):
+ if config.exists(base + ['authentication', 'mode']):
+ 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/op_mode/image_installer.py b/src/op_mode/image_installer.py
index df5d897b7..cdb84a152 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -20,6 +20,7 @@
from argparse import ArgumentParser, Namespace
from pathlib import Path
from shutil import copy, chown, rmtree, copytree
+from glob import glob
from sys import exit
from time import sleep
from typing import Union
@@ -435,6 +436,17 @@ def migrate_config() -> bool:
return False
+def copy_ssh_host_keys() -> bool:
+ """Ask user to copy SSH host keys
+
+ Returns:
+ bool: user's decision
+ """
+ if ask_yes_no('Would you like to copy SSH host keys?', default=True):
+ return True
+ return False
+
+
def cleanup(mounts: list[str] = [], remove_items: list[str] = []) -> None:
"""Clean up after installation
@@ -698,6 +710,14 @@ def add_image(image_path: str, no_prompt: bool = False) -> None:
chmod_2775(target_config_dir)
Path(f'{target_config_dir}/.vyatta_config').touch()
+ target_ssh_dir: str = f'{root_dir}/boot/{image_name}/rw/etc/ssh/'
+ if no_prompt or copy_ssh_host_keys():
+ print('Copying SSH host keys')
+ Path(target_ssh_dir).mkdir(parents=True)
+ host_keys: list[str] = glob('/etc/ssh/ssh_host*')
+ for host_key in host_keys:
+ copy(host_key, target_ssh_dir)
+
# copy system image and kernel files
print('Copying system image files')
for file in Path(f'{DIR_ISO_MOUNT}/live').iterdir():
diff --git a/src/validators/ddclient-protocol b/src/validators/ddclient-protocol
index 8f455e12e..ce5efbd52 100755
--- a/src/validators/ddclient-protocol
+++ b/src/validators/ddclient-protocol
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-ddclient -list-protocols | grep -vE 'nsupdate|cloudns|porkbun' | grep -qw $1
+ddclient -list-protocols | grep -vE 'cloudns|porkbun' | grep -qw $1
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid protocol, please choose from the supported list of protocols"
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}