summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/dynamic-dns/ddclient.conf.j22
-rw-r--r--data/templates/frr/bgpd.frr.j23
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j2164
-rw-r--r--data/templates/load-balancing/override_haproxy.conf.j214
-rw-r--r--data/templates/mdns-repeater/avahi-daemon.j26
-rw-r--r--data/templates/ocserv/ocserv_config.j213
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j22
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/container.xml.in3
-rw-r--r--interface-definitions/dns-domain-name.xml.in7
-rw-r--r--interface-definitions/dns-forwarding.xml.in3
-rw-r--r--interface-definitions/flow-accounting-conf.xml.in6
-rw-r--r--interface-definitions/high-availability.xml.in9
-rw-r--r--interface-definitions/https.xml.in3
-rw-r--r--interface-definitions/include/bgp/neighbor-update-source.xml.i3
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i9
-rw-r--r--interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i2
-rw-r--r--interface-definitions/include/constraint/host-name.xml.i2
-rw-r--r--interface-definitions/include/constraint/interface-name-with-wildcard.xml.i2
-rw-r--r--interface-definitions/include/constraint/interface-name.xml.i2
-rw-r--r--interface-definitions/include/haproxy/mode.xml.i22
-rw-r--r--interface-definitions/include/haproxy/rule-backend.xml.i131
-rw-r--r--interface-definitions/include/haproxy/rule-frontend.xml.i131
-rw-r--r--interface-definitions/include/haproxy/timeout.xml.i45
-rw-r--r--interface-definitions/include/interface/dhcp-options.xml.i4
-rw-r--r--interface-definitions/include/ipsec/local-address.xml.i3
-rw-r--r--interface-definitions/include/ipsec/remote-address.xml.i3
-rw-r--r--interface-definitions/include/listen-address-single.xml.i3
-rw-r--r--interface-definitions/include/listen-address-vrf.xml.i3
-rw-r--r--interface-definitions/include/listen-address.xml.i3
-rw-r--r--interface-definitions/include/name-server-ipv4-ipv6-port.xml.i3
-rw-r--r--interface-definitions/include/name-server-ipv4-ipv6.xml.i3
-rw-r--r--interface-definitions/include/radius-server-ipv4-ipv6.xml.i3
-rw-r--r--interface-definitions/include/static/static-route-bfd.xml.i41
-rw-r--r--interface-definitions/interfaces-openvpn.xml.in3
-rw-r--r--interface-definitions/interfaces-virtual-ethernet.xml.in1
-rw-r--r--interface-definitions/load-balancing-haproxy.xml.in248
-rw-r--r--interface-definitions/load-balancing-wan.xml.in1
-rw-r--r--interface-definitions/ntp.xml.in3
-rw-r--r--interface-definitions/policy.xml.in6
-rw-r--r--interface-definitions/protocols-bfd.xml.in6
-rw-r--r--interface-definitions/protocols-rpki.xml.in3
-rw-r--r--interface-definitions/service-mdns-repeater.xml.in26
-rw-r--r--interface-definitions/service-upnp.xml.in3
-rw-r--r--interface-definitions/snmp.xml.in9
-rw-r--r--interface-definitions/system-config-mgmt.xml.in3
-rw-r--r--interface-definitions/system-sflow.xml.in6
-rw-r--r--interface-definitions/vpn-l2tp.xml.in3
-rw-r--r--interface-definitions/vpn-openconnect.xml.in52
-rw-r--r--op-mode-definitions/show-interfaces-wwan.xml.in4
-rw-r--r--python/vyos/base.py3
-rw-r--r--python/vyos/ifconfig/ethernet.py9
-rw-r--r--python/vyos/ifconfig/interface.py2
-rw-r--r--python/vyos/util.py7
-rw-r--r--python/vyos/utils/dict.py2
-rwxr-xr-xsmoketest/scripts/cli/test_load_balancing_reverse_proxy.py112
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py18
-rwxr-xr-xsrc/conf_mode/dynamic_dns.py3
-rwxr-xr-xsrc/conf_mode/high-availability.py10
-rwxr-xr-xsrc/conf_mode/interfaces-ethernet.py9
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py181
-rwxr-xr-xsrc/conf_mode/system-syslog.py7
-rwxr-xr-xsrc/conf_mode/vpn_openconnect.py14
-rwxr-xr-xsrc/op_mode/interfaces.py34
-rw-r--r--src/tests/test_util.py16
65 files changed, 1307 insertions, 151 deletions
diff --git a/data/templates/dynamic-dns/ddclient.conf.j2 b/data/templates/dynamic-dns/ddclient.conf.j2
index c2c9b1dd6..e8ef5ac90 100644
--- a/data/templates/dynamic-dns/ddclient.conf.j2
+++ b/data/templates/dynamic-dns/ddclient.conf.j2
@@ -34,7 +34,9 @@ zone={{ config.zone }}
# DynDNS provider configuration for {{ service }}, {{ dns_record }}
protocol={{ config.protocol }},
max-interval=28d,
+{% if config.login is vyos_defined %}
login={{ config.login }},
+{% endif %}
password='{{ config.password }}',
{% if config.server is vyos_defined %}
server={{ config.server }},
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 7bd9efdce..3e101820c 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -86,6 +86,9 @@
{% if config.solo is vyos_defined %}
neighbor {{ neighbor }} solo
{% endif %}
+{% if config.enforce_first_as is vyos_defined %}
+ neighbor {{ neighbor }} enforce-first-as
+{% endif %}
{% if config.strict_capability_match is vyos_defined %}
neighbor {{ neighbor }} strict-capability-match
{% endif %}
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
new file mode 100644
index 000000000..f8e1587f8
--- /dev/null
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -0,0 +1,164 @@
+# Generated by ${vyos_conf_scripts_dir}/load-balancing-haproxy.py
+
+global
+ log /dev/log local0
+ log /dev/log local1 notice
+ chroot /var/lib/haproxy
+ stats socket /run/haproxy/admin.sock mode 660 level admin
+ stats timeout 30s
+ user haproxy
+ group haproxy
+ daemon
+
+{% if global_parameters is vyos_defined %}
+{% if global_parameters.max_connections is vyos_defined %}
+ maxconn {{ global_parameters.max_connections }}
+{% endif %}
+
+ # Default SSL material locations
+ ca-base /etc/ssl/certs
+ crt-base /etc/ssl/private
+
+{% if global_parameters.ssl_bind_ciphers is vyos_defined %}
+ # https://ssl-config.mozilla.org/#server=haproxy&version=2.6.12-1&config=intermediate&openssl=3.0.8-1&guideline=5.6
+ ssl-default-bind-ciphers {{ global_parameters.ssl_bind_ciphers | join(':') | upper }}
+{% endif %}
+ ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
+{% if global_parameters.tls_version_min is vyos_defined('1.3') %}
+ ssl-default-bind-options force-tlsv13
+{% else %}
+ ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
+{% endif %}
+{% endif %}
+
+defaults
+ log global
+ mode http
+ option dontlognull
+ timeout connect 10s
+ timeout client 50s
+ timeout server 50s
+ errorfile 400 /etc/haproxy/errors/400.http
+ errorfile 403 /etc/haproxy/errors/403.http
+ errorfile 408 /etc/haproxy/errors/408.http
+ errorfile 500 /etc/haproxy/errors/500.http
+ errorfile 502 /etc/haproxy/errors/502.http
+ errorfile 503 /etc/haproxy/errors/503.http
+ errorfile 504 /etc/haproxy/errors/504.http
+
+# Frontend
+{% if service is vyos_defined %}
+{% for front, front_config in service.items() %}
+frontend {{ front }}
+{% set ssl_front = 'ssl crt /run/haproxy/' ~ front_config.ssl.certificate ~ '.pem' if front_config.ssl.certificate is vyos_defined else '' %}
+{% if front_config.listen_address is vyos_defined %}
+{% for address in front_config.listen_address %}
+ bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_front }}
+{% endfor %}
+{% else %}
+ bind :::{{ front_config.port }} v4v6 {{ ssl_front }}
+{% endif %}
+{% if front_config.redirect_http_to_https is vyos_defined %}
+ http-request redirect scheme https unless { ssl_fc }
+{% endif %}
+{% if front_config.mode is vyos_defined %}
+ mode {{ front_config.mode }}
+{% endif %}
+{% if front_config.rule is vyos_defined %}
+{% for rule, rule_config in front_config.rule.items() %}
+ # rule {{ rule }}
+{% if rule_config.domain_name is vyos_defined and rule_config.set.backend is vyos_defined %}
+{% set rule_options = 'hdr(host)' %}
+{% if rule_config.ssl is vyos_defined %}
+{% set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %}
+{% set rule_options = ssl_rule_translate[rule_config.ssl] %}
+{% endif %}
+{% for domain in rule_config.domain_name %}
+ acl {{ rule }} {{ rule_options }} -i {{ domain }}
+{% endfor %}
+ use_backend {{ rule_config.set.backend }} if {{ rule }}
+{% endif %}
+{# path url #}
+{% if rule_config.url_path is vyos_defined and rule_config.set.redirect_location is vyos_defined %}
+{% set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %}
+{% for path, path_config in rule_config.url_path.items() %}
+{% for url in path_config %}
+ acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }}
+{% endfor %}
+{% endfor %}
+ http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }}
+{% endif %}
+{# endpath #}
+{% endfor %}
+{% endif %}
+{% if front_config.backend is vyos_defined %}
+{% for backend in front_config.backend %}
+ default_backend {{ backend }}
+{% endfor %}
+{% endif %}
+
+{% endfor %}
+{% endif %}
+
+# Backend
+{% if backend is vyos_defined %}
+{% for back, back_config in backend.items() %}
+backend {{ back }}
+{% if back_config.balance is vyos_defined %}
+{% set balance_translate = {'least-connection': 'leastconn', 'round-robin': 'roundrobin', 'source-address': 'source'} %}
+ balance {{ balance_translate[back_config.balance] }}
+{% endif %}
+{# If mode is not TCP skip Forwarded #}
+{% if back_config.mode is not vyos_defined('tcp') %}
+ option forwardfor
+ http-request set-header X-Forwarded-Port %[dst_port]
+ http-request add-header X-Forwarded-Proto https if { ssl_fc }
+{% endif %}
+{% if back_config.mode is vyos_defined %}
+ mode {{ back_config.mode }}
+{% endif %}
+{% if back_config.rule is vyos_defined %}
+{% for rule, rule_config in back_config.rule.items() %}
+{% if rule_config.domain_name is vyos_defined and rule_config.set.server is vyos_defined %}
+{% set rule_options = 'hdr(host)' %}
+{% if rule_config.ssl is vyos_defined %}
+{% set ssl_rule_translate = {'req-ssl-sni': 'req_ssl_sni', 'ssl-fc-sni': 'ssl_fc_sni', 'ssl-fc-sni-end': 'ssl_fc_sni_end'} %}
+{% set rule_options = ssl_rule_translate[rule_config.ssl] %}
+{% endif %}
+{% for domain in rule_config.domain_name %}
+ acl {{ rule }} {{ rule_options }} -i {{ domain }}
+{% endfor %}
+ use-server {{ rule_config.set.server }} if {{ rule }}
+{% endif %}
+{# path url #}
+{% if rule_config.url_path is vyos_defined and rule_config.set.redirect_location is vyos_defined %}
+{% set path_mod_translate = {'begin': '-i -m beg', 'end': '-i -m end', 'exact': ''} %}
+{% for path, path_config in rule_config.url_path.items() %}
+{% for url in path_config %}
+ acl {{ rule }} path {{ path_mod_translate[path] }} {{ url }}
+{% endfor %}
+{% endfor %}
+ http-request redirect location {{ rule_config.set.redirect_location }} code 301 if {{ rule }}
+{% endif %}
+{# endpath #}
+{% endfor %}
+{% endif %}
+{% if back_config.server is vyos_defined %}
+{% set ssl_back = 'ssl ca-file /run/haproxy/' ~ back_config.ssl.ca_certificate ~ '.pem' if back_config.ssl.ca_certificate is vyos_defined else '' %}
+{% for server, server_config in back_config.server.items() %}
+ server {{ server }} {{ server_config.address }}:{{ server_config.port }}{{ ' check' if server_config.check is vyos_defined }}{{ ' send-proxy' if server_config.send_proxy is vyos_defined }}{{ ' send-proxy-v2' if server_config.send_proxy_v2 is vyos_defined }} {{ ssl_back }}
+{% endfor %}
+{% endif %}
+{% if back_config.timeout.check is vyos_defined %}
+ timeout check {{ back_config.timeout.check }}
+{% endif %}
+{% if back_config.timeout.connect is vyos_defined %}
+ timeout connect {{ back_config.timeout.connect }}
+{% endif %}
+{% if back_config.timeout.server is vyos_defined %}
+ timeout server {{ back_config.timeout.server }}
+{% endif %}
+
+{% endfor %}
+{% endif %}
+
diff --git a/data/templates/load-balancing/override_haproxy.conf.j2 b/data/templates/load-balancing/override_haproxy.conf.j2
new file mode 100644
index 000000000..395b5d279
--- /dev/null
+++ b/data/templates/load-balancing/override_haproxy.conf.j2
@@ -0,0 +1,14 @@
+{% set haproxy_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %}
+[Unit]
+StartLimitIntervalSec=0
+After=vyos-router.service
+ConditionPathExists=/run/haproxy/haproxy.cfg
+
+[Service]
+EnvironmentFile=
+Environment=
+Environment="CONFIG=/run/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid" "EXTRAOPTS=-S /run/haproxy-master.sock"
+ExecStart=
+ExecStart={{ haproxy_command }}/usr/sbin/haproxy -Ws -f /run/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock
+Restart=always
+RestartSec=10
diff --git a/data/templates/mdns-repeater/avahi-daemon.j2 b/data/templates/mdns-repeater/avahi-daemon.j2
index 65bb5a306..3aaa7fc82 100644
--- a/data/templates/mdns-repeater/avahi-daemon.j2
+++ b/data/templates/mdns-repeater/avahi-daemon.j2
@@ -2,6 +2,9 @@
use-ipv4=yes
use-ipv6=yes
allow-interfaces={{ interface | join(', ') }}
+{% if browse_domain is vyos_defined and browse_domain | length %}
+browse-domains={{ browse_domain | join(', ') }}
+{% endif %}
disallow-other-stacks=no
[wide-area]
@@ -16,3 +19,6 @@ publish-workstation=no
[reflector]
enable-reflector=yes
+{% if allow_service is vyos_defined and allow_service | length %}
+reflect-filters={{ allow_service | join(', ') }}
+{% endif %}
diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index aa1073bca..1401b8b26 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -16,6 +16,12 @@ acct = "radius [config=/run/ocserv/radiusclient.conf]"
{% if "radius" in authentication.mode %}
auth = "radius [config=/run/ocserv/radiusclient.conf{{ ',groupconfig=true' if authentication.radius.groupconfig is vyos_defined else '' }}]"
+{% if authentication.identity_based_config.disabled is not vyos_defined %}
+{% if "group" in authentication.identity_based_config.mode %}
+config-per-group = {{ authentication.identity_based_config.directory }}
+default-group-config = {{ authentication.identity_based_config.default_config }}
+{% endif %}
+{% endif %}
{% elif "local" in authentication.mode %}
{% if authentication.mode.local == "password-otp" %}
auth = "plain[passwd=/run/ocserv/ocpasswd,otp=/run/ocserv/users.oath]"
@@ -28,6 +34,13 @@ auth = "plain[/run/ocserv/ocpasswd]"
auth = "plain[/run/ocserv/ocpasswd]"
{% endif %}
+{% if "identity_based_config" in authentication %}
+{% if "user" in authentication.identity_based_config.mode %}
+config-per-user = {{ authentication.identity_based_config.directory }}
+default-user-config = {{ authentication.identity_based_config.default_config }}
+{% endif %}
+{% endif %}
+
{% if ssl.certificate is vyos_defined %}
server-cert = /run/ocserv/cert.pem
server-key = /run/ocserv/cert.key
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index 0460ae5f0..5352fc367 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -49,7 +49,7 @@ $outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archiv
{% set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
{% endfor %}
{% if host_options.protocol is vyos_defined('tcp') %}
-{% if host_options.oct_count is vyos_defined %}
+{% if host_options.format.octet_counted is vyos_defined %}
{{ tmp | join(';') }} @@(o){{ host_name | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format
{% else %}
{{ tmp | join(';') }} @@{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}
diff --git a/debian/control b/debian/control
index 4a2706fc3..ec08968c9 100644
--- a/debian/control
+++ b/debian/control
@@ -65,6 +65,7 @@ Depends:
fuse-overlayfs,
libpam-google-authenticator,
grc,
+ haproxy,
hostapd,
hsflowd,
hvinfo,
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 9b6d2369d..6651fc642 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -201,8 +201,7 @@
<description>IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/dns-domain-name.xml.in b/interface-definitions/dns-domain-name.xml.in
index e93c49ebd..ef34ecbf5 100644
--- a/interface-definitions/dns-domain-name.xml.in
+++ b/interface-definitions/dns-domain-name.xml.in
@@ -23,8 +23,7 @@
</valueHelp>
<multi/>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
#include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
@@ -74,9 +73,9 @@
<properties>
<help>Host name for static address mapping</help>
<constraint>
- <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
+ #include <include/constraint/host-name.xml.i>
</constraint>
- <constraintErrorMessage>invalid hostname</constraintErrorMessage>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
</properties>
<children>
<leafNode name="alias">
diff --git a/interface-definitions/dns-forwarding.xml.in b/interface-definitions/dns-forwarding.xml.in
index de6991e06..ced1c9c31 100644
--- a/interface-definitions/dns-forwarding.xml.in
+++ b/interface-definitions/dns-forwarding.xml.in
@@ -670,8 +670,7 @@
</valueHelp>
<multi/>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<defaultValue>0.0.0.0 ::</defaultValue>
diff --git a/interface-definitions/flow-accounting-conf.xml.in b/interface-definitions/flow-accounting-conf.xml.in
index 878566b3f..40a9bb423 100644
--- a/interface-definitions/flow-accounting-conf.xml.in
+++ b/interface-definitions/flow-accounting-conf.xml.in
@@ -230,8 +230,7 @@
<description>IPv6 server to export NetFlow</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
@@ -408,8 +407,7 @@
<description>IPv6 server to export sFlow</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 94253def3..c5e46ed38 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -129,8 +129,7 @@
<description>IPv6 ping target address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
@@ -156,8 +155,7 @@
<description>IPv6 hello source address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
@@ -173,8 +171,7 @@
<description>IPv6 unicast peer address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in
index cf30ab2be..5430193b5 100644
--- a/interface-definitions/https.xml.in
+++ b/interface-definitions/https.xml.in
@@ -36,8 +36,7 @@
<description>any</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<regex>\*</regex>
</constraint>
</properties>
diff --git a/interface-definitions/include/bgp/neighbor-update-source.xml.i b/interface-definitions/include/bgp/neighbor-update-source.xml.i
index c6aa776c2..92e817166 100644
--- a/interface-definitions/include/bgp/neighbor-update-source.xml.i
+++ b/interface-definitions/include/bgp/neighbor-update-source.xml.i
@@ -20,8 +20,7 @@
<description>Interface as route source</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
#include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 527eaf991..d69fd7dab 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -940,8 +940,7 @@
<description>Interface name</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
#include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
@@ -1017,6 +1016,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="enforce-first-as">
+ <properties>
+ <help>Ensure the first AS in the AS path matches the peer AS</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="strict-capability-match">
<properties>
<help>Enable strict capability negotiation</help>
diff --git a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
index eb568d7d9..ba097c6b5 100644
--- a/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
+++ b/interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i
@@ -1,3 +1,3 @@
-<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.in -->
+<!-- include start from include/constraint/alpha-numeric-hyphen-underscore.xml.i -->
<regex>[-_a-zA-Z0-9]+</regex>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/host-name.xml.i b/interface-definitions/include/constraint/host-name.xml.i
index 202c200f4..cc9740c16 100644
--- a/interface-definitions/include/constraint/host-name.xml.i
+++ b/interface-definitions/include/constraint/host-name.xml.i
@@ -1,3 +1,3 @@
-<!-- include start from constraint/host-name.xml.in -->
+<!-- include start from constraint/host-name.xml.i -->
<regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i
index 09867b380..adff530b6 100644
--- a/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i
+++ b/interface-definitions/include/constraint/interface-name-with-wildcard.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from constraint/interface-name-with-wildcard.xml.in -->
+<!-- include start from constraint/interface-name-with-wildcard.xml.i -->
<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|lo</regex>
<validator name="file-path --lookup-path /sys/class/net --directory"/>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/interface-name.xml.i b/interface-definitions/include/constraint/interface-name.xml.i
index e540e4418..1b14eabf5 100644
--- a/interface-definitions/include/constraint/interface-name.xml.i
+++ b/interface-definitions/include/constraint/interface-name.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from constraint/interface-name.xml.in -->
+<!-- include start from constraint/interface-name.xml.i -->
<regex>(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)[0-9]+(.\d+)?|lo</regex>
<validator name="file-path --lookup-path /sys/class/net --directory"/>
<!-- include end -->
diff --git a/interface-definitions/include/haproxy/mode.xml.i b/interface-definitions/include/haproxy/mode.xml.i
new file mode 100644
index 000000000..672ea65b4
--- /dev/null
+++ b/interface-definitions/include/haproxy/mode.xml.i
@@ -0,0 +1,22 @@
+<!-- include start from haproxy/mode.xml.i -->
+<leafNode name="mode">
+ <properties>
+ <help>Proxy mode</help>
+ <completionHelp>
+ <list>http tcp</list>
+ </completionHelp>
+ <constraintErrorMessage>invalid value</constraintErrorMessage>
+ <valueHelp>
+ <format>http</format>
+ <description>HTTP proxy mode</description>
+ </valueHelp>
+ <valueHelp>
+ <format>tcp</format>
+ <description>TCP proxy mode</description>
+ </valueHelp>
+ <constraint>
+ <regex>(http|tcp)</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/rule-backend.xml.i b/interface-definitions/include/haproxy/rule-backend.xml.i
new file mode 100644
index 000000000..a6832d693
--- /dev/null
+++ b/interface-definitions/include/haproxy/rule-backend.xml.i
@@ -0,0 +1,131 @@
+<!-- include start from haproxy/rule.xml.i -->
+<tagNode name="rule">
+ <properties>
+ <help>Proxy rule number</help>
+ <valueHelp>
+ <format>u32:1-10000</format>
+ <description>Number for this proxy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-10000"/>
+ </constraint>
+ <constraintErrorMessage>Proxy rule number must be between 1 and 10000</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="domain-name">
+ <properties>
+ <help>Domain name to match</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain address to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <node name="set">
+ <properties>
+ <help>Proxy modifications</help>
+ </properties>
+ <children>
+ <leafNode name="redirect-location">
+ <properties>
+ <help>Set URL location</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Set URL location</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="server">
+ <properties>
+ <help>Server name</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="ssl">
+ <properties>
+ <help>SSL match options</help>
+ <completionHelp>
+ <list>req-ssl-sni ssl-fc-sni</list>
+ </completionHelp>
+ <valueHelp>
+ <format>req-ssl-sni</format>
+ <description>SSL Server Name Indication (SNI) request match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ssl-fc-sni</format>
+ <description>SSL frontend connection Server Name Indication match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ssl-fc-sni-end</format>
+ <description>SSL frontend match end of connection Server Name Indication</description>
+ </valueHelp>
+ <constraint>
+ <regex>(req-ssl-sni|ssl-fc-sni|ssl-fc-sni-end)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="url-path">
+ <properties>
+ <help>URL path match</help>
+ </properties>
+ <children>
+ <leafNode name="begin">
+ <properties>
+ <help>Begin URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Begin URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="end">
+ <properties>
+ <help>End URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>End URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="exact">
+ <properties>
+ <help>Exactly URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Exactly URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/rule-frontend.xml.i b/interface-definitions/include/haproxy/rule-frontend.xml.i
new file mode 100644
index 000000000..001ae2d80
--- /dev/null
+++ b/interface-definitions/include/haproxy/rule-frontend.xml.i
@@ -0,0 +1,131 @@
+<!-- include start from haproxy/rule.xml.i -->
+<tagNode name="rule">
+ <properties>
+ <help>Proxy rule number</help>
+ <valueHelp>
+ <format>u32:1-10000</format>
+ <description>Number for this proxy rule</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-10000"/>
+ </constraint>
+ <constraintErrorMessage>Proxy rule number must be between 1 and 10000</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="domain-name">
+ <properties>
+ <help>Domain name to match</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain address to match</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <node name="set">
+ <properties>
+ <help>Proxy modifications</help>
+ </properties>
+ <children>
+ <leafNode name="redirect-location">
+ <properties>
+ <help>Set URL location</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Set URL location</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="backend">
+ <properties>
+ <help>Backend name</help>
+ <constraint>
+ <regex>[-_a-zA-Z0-9]+</regex>
+ </constraint>
+ <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="ssl">
+ <properties>
+ <help>SSL match options</help>
+ <completionHelp>
+ <list>req-ssl-sni ssl-fc-sni</list>
+ </completionHelp>
+ <valueHelp>
+ <format>req-ssl-sni</format>
+ <description>SSL Server Name Indication (SNI) request match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ssl-fc-sni</format>
+ <description>SSL frontend connection Server Name Indication match</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ssl-fc-sni-end</format>
+ <description>SSL frontend match end of connection Server Name Indication</description>
+ </valueHelp>
+ <constraint>
+ <regex>(req-ssl-sni|ssl-fc-sni|ssl-fc-sni-end)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <node name="url-path">
+ <properties>
+ <help>URL path match</help>
+ </properties>
+ <children>
+ <leafNode name="begin">
+ <properties>
+ <help>Begin URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Begin URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="end">
+ <properties>
+ <help>End URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>End URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="exact">
+ <properties>
+ <help>Exactly URL match</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Exactly URL</description>
+ </valueHelp>
+ <constraint>
+ <regex>^\/[\w\-.\/]+$</regex>
+ </constraint>
+ <constraintErrorMessage>Incorrect URL format</constraintErrorMessage>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/haproxy/timeout.xml.i b/interface-definitions/include/haproxy/timeout.xml.i
new file mode 100644
index 000000000..250b35683
--- /dev/null
+++ b/interface-definitions/include/haproxy/timeout.xml.i
@@ -0,0 +1,45 @@
+<!-- include start from haproxy/timeout.xml.i -->
+<node name="timeout">
+ <properties>
+ <help>Tiemout options</help>
+ </properties>
+ <children>
+ <leafNode name="check">
+ <properties>
+ <help>Timeout in seconds for established connections</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Check timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="connect">
+ <properties>
+ <help>Set the maximum time to wait for a connection attempt to a server to succeed</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Connect timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="server">
+ <properties>
+ <help>Set the maximum inactivity time on the server side</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Server timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i
index 2ed5fd403..8027769ff 100644
--- a/interface-definitions/include/interface/dhcp-options.xml.i
+++ b/interface-definitions/include/interface/dhcp-options.xml.i
@@ -12,6 +12,10 @@
<leafNode name="host-name">
<properties>
<help>Override system host-name sent to DHCP server</help>
+ <constraint>
+ #include <include/constraint/host-name.xml.i>
+ </constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="mtu">
diff --git a/interface-definitions/include/ipsec/local-address.xml.i b/interface-definitions/include/ipsec/local-address.xml.i
index 9d267f3f7..71f514950 100644
--- a/interface-definitions/include/ipsec/local-address.xml.i
+++ b/interface-definitions/include/ipsec/local-address.xml.i
@@ -19,8 +19,7 @@
<description>Allow any IPv4 address present on the system to be used for VPN</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<regex>(any)</regex>
</constraint>
</properties>
diff --git a/interface-definitions/include/ipsec/remote-address.xml.i b/interface-definitions/include/ipsec/remote-address.xml.i
index ba96290d0..91decba3c 100644
--- a/interface-definitions/include/ipsec/remote-address.xml.i
+++ b/interface-definitions/include/ipsec/remote-address.xml.i
@@ -19,8 +19,7 @@
<description>Allow any IP address of the remote peer</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="fqdn"/>
<regex>(any)</regex>
</constraint>
diff --git a/interface-definitions/include/listen-address-single.xml.i b/interface-definitions/include/listen-address-single.xml.i
index 30293b338..6cc5aef0a 100644
--- a/interface-definitions/include/listen-address-single.xml.i
+++ b/interface-definitions/include/listen-address-single.xml.i
@@ -14,8 +14,7 @@
<description>IPv6 address to listen for incoming connections</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="ipv6-link-local"/>
</constraint>
</properties>
diff --git a/interface-definitions/include/listen-address-vrf.xml.i b/interface-definitions/include/listen-address-vrf.xml.i
index 8c2bdce70..23ecc2476 100644
--- a/interface-definitions/include/listen-address-vrf.xml.i
+++ b/interface-definitions/include/listen-address-vrf.xml.i
@@ -14,8 +14,7 @@
<description>IPv6 address to listen for incoming connections</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/listen-address.xml.i b/interface-definitions/include/listen-address.xml.i
index 48003dbf2..2454f43ff 100644
--- a/interface-definitions/include/listen-address.xml.i
+++ b/interface-definitions/include/listen-address.xml.i
@@ -15,8 +15,7 @@
</valueHelp>
<multi/>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="ipv6-link-local"/>
</constraint>
</properties>
diff --git a/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i
index fb0a4f4ae..b326a6537 100644
--- a/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i
+++ b/interface-definitions/include/name-server-ipv4-ipv6-port.xml.i
@@ -11,8 +11,7 @@
<description>Domain Name Server (DNS) IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/name-server-ipv4-ipv6.xml.i b/interface-definitions/include/name-server-ipv4-ipv6.xml.i
index 14973234b..cf483e5d9 100644
--- a/interface-definitions/include/name-server-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/name-server-ipv4-ipv6.xml.i
@@ -11,8 +11,7 @@
<description>Domain Name Server (DNS) IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
index c593512b4..efd418bb2 100644
--- a/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
+++ b/interface-definitions/include/radius-server-ipv4-ipv6.xml.i
@@ -16,8 +16,7 @@
<description>RADIUS server IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/include/static/static-route-bfd.xml.i b/interface-definitions/include/static/static-route-bfd.xml.i
index a05a08d12..d588b369f 100644
--- a/interface-definitions/include/static/static-route-bfd.xml.i
+++ b/interface-definitions/include/static/static-route-bfd.xml.i
@@ -10,28 +10,27 @@
<help>Use BFD multi hop session</help>
</properties>
<children>
- <tagNode name="source">
- <properties>
- <help>Use source for BFD session</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 source address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 source address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- </constraint>
- </properties>
- <children>
- #include <include/bfd/profile.xml.i>
- </children>
- </tagNode>
+ <tagNode name="source">
+ <properties>
+ <help>Use source for BFD session</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 source address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 source address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/bfd/profile.xml.i>
+ </children>
+ </tagNode>
</children>
</node>
</children>
</node>
-<!-- include end --> \ No newline at end of file
+<!-- include end -->
diff --git a/interface-definitions/interfaces-openvpn.xml.in b/interface-definitions/interfaces-openvpn.xml.in
index cf0ff497c..4e061c3e6 100644
--- a/interface-definitions/interfaces-openvpn.xml.in
+++ b/interface-definitions/interfaces-openvpn.xml.in
@@ -333,8 +333,7 @@
<description>Remote end IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/interfaces-virtual-ethernet.xml.in b/interface-definitions/interfaces-virtual-ethernet.xml.in
index a5702bfc0..864f658da 100644
--- a/interface-definitions/interfaces-virtual-ethernet.xml.in
+++ b/interface-definitions/interfaces-virtual-ethernet.xml.in
@@ -22,7 +22,6 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
- #include <include/interface/netns.xml.i>
<leafNode name="peer-name">
<properties>
<help>Virtual ethernet peer interface name</help>
diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in
new file mode 100644
index 000000000..f955a2fb7
--- /dev/null
+++ b/interface-definitions/load-balancing-haproxy.xml.in
@@ -0,0 +1,248 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="load-balancing">
+ <children>
+ <node name="reverse-proxy" owner="${vyos_conf_scripts_dir}/load-balancing-haproxy.py">
+ <properties>
+ <help>Configure reverse-proxy</help>
+ </properties>
+ <children>
+ <tagNode name="service">
+ <properties>
+ <help>Frontend service name</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Server name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="backend">
+ <properties>
+ <help>Backend member</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of reverse-proxy backend system</description>
+ </valueHelp>
+ <completionHelp>
+ <path>load-balancing reverse-proxy backend</path>
+ </completionHelp>
+ <multi/>
+ </properties>
+ </leafNode>
+ #include <include/generic-description.xml.i>
+ #include <include/listen-address.xml.i>
+ #include <include/haproxy/mode.xml.i>
+ #include <include/port-number.xml.i>
+ #include <include/haproxy/rule-frontend.xml.i>
+ <leafNode name="redirect-http-to-https">
+ <properties>
+ <help>Redirect HTTP to HTTPS</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="ssl">
+ <properties>
+ <help>SSL Certificate, SSL Key and CA</help>
+ </properties>
+ <children>
+ #include <include/pki/certificate.xml.i>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ <tagNode name="backend">
+ <properties>
+ <help>Backend server name</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Backend name must be alphanumeric and can contain hyphen and underscores</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="balance">
+ <properties>
+ <help>Load-balancing algorithm</help>
+ <completionHelp>
+ <list>source-address round-robin least-connection</list>
+ </completionHelp>
+ <valueHelp>
+ <format>source-address</format>
+ <description>Based on hash of source IP address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>round-robin</format>
+ <description>Round robin</description>
+ </valueHelp>
+ <valueHelp>
+ <format>least-connection</format>
+ <description>Least connection</description>
+ </valueHelp>
+ <constraint>
+ <regex>(source-address|round-robin|least-connection)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>round-robin</defaultValue>
+ </leafNode>
+ #include <include/generic-description.xml.i>
+ #include <include/haproxy/mode.xml.i>
+ <node name="parameters">
+ <properties>
+ <help>Backend parameters</help>
+ </properties>
+ <children>
+ <leafNode name="http-check">
+ <properties>
+ <help>HTTP health check</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/haproxy/rule-backend.xml.i>
+ <tagNode name="server">
+ <properties>
+ <help>Backend server name</help>
+ </properties>
+ <children>
+ <leafNode name="address">
+ <properties>
+ <help>Backend server address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 unicast peer address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 unicast peer address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="check">
+ <properties>
+ <help>Active health check backend server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="send-proxy">
+ <properties>
+ <help>Send a Proxy Protocol version 1 header (text format)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="send-proxy-v2">
+ <properties>
+ <help>Send a Proxy Protocol version 2 header (binary format)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="ssl">
+ <properties>
+ <help>SSL Certificate, SSL Key and CA</help>
+ </properties>
+ <children>
+ #include <include/pki/ca-certificate.xml.i>
+ </children>
+ </node>
+ #include <include/haproxy/timeout.xml.i>
+ </children>
+ </tagNode>
+ <node name="global-parameters">
+ <properties>
+ <help>Global perfomance parameters and limits</help>
+ </properties>
+ <children>
+ <leafNode name="max-connections">
+ <properties>
+ <help>Maximum allowed connections</help>
+ <valueHelp>
+ <format>u32:1-2000000</format>
+ <description>Maximum allowed connections</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-2000000"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="ssl-bind-ciphers">
+ <properties>
+ <help>Cipher algorithms ("cipher suite") used during SSL/TLS handshake for all frontend servers</help>
+ <completionHelp>
+ <list>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ecdhe-ecdsa-aes128-gcm-sha256</format>
+ <description>ecdhe-ecdsa-aes128-gcm-sha256</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdhe-rsa-aes128-gcm-sha256</format>
+ <description>ecdhe-rsa-aes128-gcm-sha256</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdhe-ecdsa-aes256-gcm-sha384</format>
+ <description>ecdhe-ecdsa-aes256-gcm-sha384</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdhe-rsa-aes256-gcm-sha384</format>
+ <description>ecdhe-rsa-aes256-gcm-sha384</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdhe-ecdsa-chacha20-poly1305</format>
+ <description>ecdhe-ecdsa-chacha20-poly1305</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ecdhe-rsa-chacha20-poly1305</format>
+ <description>ecdhe-rsa-chacha20-poly1305</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhe-rsa-aes128-gcm-sha256</format>
+ <description>dhe-rsa-aes128-gcm-sha256</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhe-rsa-aes256-gcm-sha384</format>
+ <description>dhe-rsa-aes256-gcm-sha384</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ecdhe-ecdsa-aes128-gcm-sha256|ecdhe-rsa-aes128-gcm-sha256|ecdhe-ecdsa-aes256-gcm-sha384|ecdhe-rsa-aes256-gcm-sha384|ecdhe-ecdsa-chacha20-poly1305|ecdhe-rsa-chacha20-poly1305|dhe-rsa-aes128-gcm-sha256|dhe-rsa-aes256-gcm-sha384)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ <defaultValue>ecdhe-ecdsa-aes128-gcm-sha256 ecdhe-rsa-aes128-gcm-sha256 ecdhe-ecdsa-aes256-gcm-sha384 ecdhe-rsa-aes256-gcm-sha384 ecdhe-ecdsa-chacha20-poly1305 ecdhe-rsa-chacha20-poly1305 dhe-rsa-aes128-gcm-sha256 dhe-rsa-aes256-gcm-sha384</defaultValue>
+ </leafNode>
+ <leafNode name="tls-version-min">
+ <properties>
+ <help>Specify the minimum required TLS version</help>
+ <completionHelp>
+ <list>1.2 1.3</list>
+ </completionHelp>
+ <valueHelp>
+ <format>1.2</format>
+ <description>TLS v1.2</description>
+ </valueHelp>
+ <valueHelp>
+ <format>1.3</format>
+ <description>TLS v1.3</description>
+ </valueHelp>
+ <constraint>
+ <regex>(1.2|1.3)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>1.3</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ #include <include/interface/vrf.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/load-balancing-wan.xml.in b/interface-definitions/load-balancing-wan.xml.in
index 3a2c111ac..c12cab22a 100644
--- a/interface-definitions/load-balancing-wan.xml.in
+++ b/interface-definitions/load-balancing-wan.xml.in
@@ -179,6 +179,7 @@
<regex>(ping|ttl|user-defined)</regex>
</constraint>
</properties>
+ <defaultValue>ping</defaultValue>
</leafNode>
</children>
</tagNode>
diff --git a/interface-definitions/ntp.xml.in b/interface-definitions/ntp.xml.in
index 558204a06..2275dd61c 100644
--- a/interface-definitions/ntp.xml.in
+++ b/interface-definitions/ntp.xml.in
@@ -25,8 +25,7 @@
<description>Fully qualified domain name of NTP server</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="fqdn"/>
</constraint>
</properties>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 02828c4f6..aa39950c2 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -966,8 +966,7 @@
<description>Peer IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
@@ -1533,8 +1532,7 @@
<description>IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/protocols-bfd.xml.in b/interface-definitions/protocols-bfd.xml.in
index edbac8d0e..9048cf5c2 100644
--- a/interface-definitions/protocols-bfd.xml.in
+++ b/interface-definitions/protocols-bfd.xml.in
@@ -21,8 +21,7 @@
<description>BFD peer IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
@@ -48,8 +47,7 @@
<description>Local IPv6 address used to connect to the peer</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/protocols-rpki.xml.in b/interface-definitions/protocols-rpki.xml.in
index c41fa54f2..e9fd04b5f 100644
--- a/interface-definitions/protocols-rpki.xml.in
+++ b/interface-definitions/protocols-rpki.xml.in
@@ -23,8 +23,7 @@
<description>Fully qualified domain name of RPKI server</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="fqdn"/>
</constraint>
</properties>
diff --git a/interface-definitions/service-mdns-repeater.xml.in b/interface-definitions/service-mdns-repeater.xml.in
index 9a94f1488..4aab9a558 100644
--- a/interface-definitions/service-mdns-repeater.xml.in
+++ b/interface-definitions/service-mdns-repeater.xml.in
@@ -15,6 +15,32 @@
<children>
#include <include/generic-disable-node.xml.i>
#include <include/generic-interface-multi.xml.i>
+ <leafNode name="browse-domain">
+ <properties>
+ <help>mDNS browsing domains in addition to the default one</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>mDNS browsing domain</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="allow-service">
+ <properties>
+ <help>Allowed mDNS services to be repeated</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>mDNS service</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-_.a-zA-Z0-9]+</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
<leafNode name="vrrp-disable">
<properties>
<help>Disables mDNS repeater on VRRP interfaces not in MASTER state</help>
diff --git a/interface-definitions/service-upnp.xml.in b/interface-definitions/service-upnp.xml.in
index 1b2e00d91..20e01bfbd 100644
--- a/interface-definitions/service-upnp.xml.in
+++ b/interface-definitions/service-upnp.xml.in
@@ -120,9 +120,8 @@
<multi/>
<constraint>
#include <include/constraint/interface-name.xml.i>
- <validator name="ipv4-address"/>
+ <validator name="ip-address"/>
<validator name="ipv4-prefix"/>
- <validator name="ipv6-address"/>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index 6527cabd6..0851b8389 100644
--- a/interface-definitions/snmp.xml.in
+++ b/interface-definitions/snmp.xml.in
@@ -94,8 +94,7 @@
<description>IPv6 address to listen for incoming SNMP requests</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
@@ -166,8 +165,7 @@
<description>IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
</leafNode>
@@ -183,8 +181,7 @@
<description>IPv6 address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/system-config-mgmt.xml.in b/interface-definitions/system-config-mgmt.xml.in
index 716332d2a..de5a8cc16 100644
--- a/interface-definitions/system-config-mgmt.xml.in
+++ b/interface-definitions/system-config-mgmt.xml.in
@@ -30,8 +30,7 @@
<properties>
<help>Source address or interface for archive server connections</help>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
#include <include/constraint/interface-name.xml.i>
</constraint>
</properties>
diff --git a/interface-definitions/system-sflow.xml.in b/interface-definitions/system-sflow.xml.in
index 9c748c24a..c5152abe9 100644
--- a/interface-definitions/system-sflow.xml.in
+++ b/interface-definitions/system-sflow.xml.in
@@ -25,8 +25,7 @@
<description>sFlow IPv6 agent address</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
<validator name="ipv6-link-local"/>
</constraint>
</properties>
@@ -97,8 +96,7 @@
<description>IPv6 server to export sFlow</description>
</valueHelp>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
+ <validator name="ip-address"/>
</constraint>
</properties>
<children>
diff --git a/interface-definitions/vpn-l2tp.xml.in b/interface-definitions/vpn-l2tp.xml.in
index ec186cd23..ee0edc3e3 100644
--- a/interface-definitions/vpn-l2tp.xml.in
+++ b/interface-definitions/vpn-l2tp.xml.in
@@ -38,8 +38,9 @@
<properties>
<help>Sent to the client (LAC) in the Host-Name attribute</help>
<constraint>
- <regex>[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]</regex>
+ #include <include/constraint/host-name.xml.i>
</constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index a426f604d..75c64a99a 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -71,6 +71,58 @@
</leafNode>
</children>
</node>
+ <node name="identity-based-config">
+ <properties>
+ <help>Include configuration file by username or RADIUS group attribute</help>
+ </properties>
+ <children>
+ #include <include/generic-disable-node.xml.i>
+ <leafNode name="mode">
+ <properties>
+ <help>Select per user or per group configuration file - ignored if authentication group is configured</help>
+ <completionHelp>
+ <list>user group</list>
+ </completionHelp>
+ <valueHelp>
+ <format>user</format>
+ <description>Match configuration file on username</description>
+ </valueHelp>
+ <valueHelp>
+ <format>group</format>
+ <description>Match RADIUS response class attribute as file name</description>
+ </valueHelp>
+ <constraint>
+ <regex>(user|group)</regex>
+ </constraint>
+ <constraintErrorMessage>Invalid mode, must be either user or group</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="directory">
+ <properties>
+ <help>Directory to containing configuration files</help>
+ <valueHelp>
+ <format>path</format>
+ <description>Path to configuration directory, must be under /config/auth</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--directory --parent-dir /config/auth --strict"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="default-config">
+ <properties>
+ <help>Default configuration if discrete config could not be found</help>
+ <valueHelp>
+ <format>filename</format>
+ <description>Default configuration filename, must be under /config/auth</description>
+ </valueHelp>
+ <constraint>
+ <validator name="file-path" argument="--file --parent-dir /config/auth --strict"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="group">
<properties>
<help>Group that a client is allowed to select (from a list). Maps to RADIUS Class attribute.</help>
diff --git a/op-mode-definitions/show-interfaces-wwan.xml.in b/op-mode-definitions/show-interfaces-wwan.xml.in
index 45558115b..3682282a3 100644
--- a/op-mode-definitions/show-interfaces-wwan.xml.in
+++ b/op-mode-definitions/show-interfaces-wwan.xml.in
@@ -86,13 +86,13 @@
<properties>
<help>Show Wireless Modem (WWAN) interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wirelessmodem</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show_summary --intf-type=wwan</command>
<children>
<leafNode name="detail">
<properties>
<help>Show detailed Wireless Modem (WWAN( interface information</help>
</properties>
- <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wirelessmodem</command>
+ <command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=wwan</command>
</leafNode>
</children>
</node>
diff --git a/python/vyos/base.py b/python/vyos/base.py
index c1acfd060..9b93cb2f2 100644
--- a/python/vyos/base.py
+++ b/python/vyos/base.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2018-2022 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
@@ -41,6 +41,7 @@ class BaseWarning:
isfirstmessage = False
initial_indent = self.standardindent
print(f'{mes}')
+ print('')
class Warning():
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 6a49c022a..30bea3b86 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -374,10 +374,11 @@ class EthernetIf(Interface):
self.set_tso(dict_search('offload.tso', config) != None)
# Set physical interface speed and duplex
- if {'speed', 'duplex'} <= set(config):
- speed = config.get('speed')
- duplex = config.get('duplex')
- self.set_speed_duplex(speed, duplex)
+ if 'speed_duplex_changed' in config:
+ if {'speed', 'duplex'} <= set(config):
+ speed = config.get('speed')
+ duplex = config.get('duplex')
+ self.set_speed_duplex(speed, duplex)
# Set interface ring buffer
if 'ring_buffer' in config:
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 2f1d5eb96..f62b9f7d2 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -532,7 +532,7 @@ class Interface(Control):
return None
# As a PoC we only allow 'dummy' interfaces
- if not ('dum' in self.ifname or 'veth' in self.ifname):
+ if 'dum' not in self.ifname:
return None
# Check if interface realy exists in namespace
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 0593184cc..d5330db13 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -1146,13 +1146,6 @@ def sysctl_write(name, value):
return True
return False
-# approach follows a discussion in:
-# https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
-def camel_to_snake_case(name: str) -> str:
- pattern = r'\d+|[A-Z]?[a-z]+|\W|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)'
- words = re.findall(pattern, name)
- return '_'.join(map(str.lower, words))
-
def load_as_module(name: str, path: str):
import importlib.util
diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py
index 4afc9f54e..7c93deef6 100644
--- a/python/vyos/utils/dict.py
+++ b/python/vyos/utils/dict.py
@@ -253,4 +253,4 @@ def check_mutually_exclusive_options(d, keys, required=False):
raise ValueError(f"Options {orig_keys} are mutually-exclusive but more than one of them is present: {orig_present_keys}")
if required and (len(present_keys) < 1):
- raise ValueError(f"At least one of the following options is required: {orig_present_keys}")
+ raise ValueError(f"At least one of the following options is required: {orig_keys}")
diff --git a/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py
new file mode 100755
index 000000000..23a681321
--- /dev/null
+++ b/smoketest/scripts/cli/test_load_balancing_reverse_proxy.py
@@ -0,0 +1,112 @@
+#!/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 unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.util import process_named_running
+from vyos.util import read_file
+
+PROCESS_NAME = 'haproxy'
+HAPROXY_CONF = '/run/haproxy/haproxy.cfg'
+base_path = ['load-balancing', 'reverse-proxy']
+proxy_interface = 'eth1'
+
+
+class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
+ def tearDown(self):
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+ self.cli_delete(['interfaces', 'ethernet', proxy_interface, 'address'])
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # Process must be terminated after deleting the config
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_01_lb_reverse_proxy_domain(self):
+ domains_bk_first = ['n1.example.com', 'n2.example.com', 'n3.example.com']
+ domain_bk_second = 'n5.example.com'
+ frontend = 'https_front'
+ front_port = '4433'
+ bk_server_first = '192.0.2.11'
+ bk_server_second = '192.0.2.12'
+ bk_first_name = 'bk-01'
+ bk_second_name = 'bk-02'
+ bk_server_port = '9090'
+ mode = 'http'
+ rule_ten = '10'
+ rule_twenty = '20'
+ send_proxy = 'send-proxy'
+ max_connections = '1000'
+
+ back_base = base_path + ['backend']
+
+ self.cli_set(base_path + ['service', frontend, 'mode', mode])
+ self.cli_set(base_path + ['service', frontend, 'port', front_port])
+ for domain in domains_bk_first:
+ self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'domain-name', domain])
+ self.cli_set(base_path + ['service', frontend, 'rule', rule_ten, 'set', 'backend', bk_first_name])
+ self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'domain-name', domain_bk_second])
+ self.cli_set(base_path + ['service', frontend, 'rule', rule_twenty, 'set', 'backend', bk_second_name])
+
+ self.cli_set(back_base + [bk_first_name, 'mode', mode])
+ self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'address', bk_server_first])
+ self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, 'port', bk_server_port])
+ self.cli_set(back_base + [bk_first_name, 'server', bk_first_name, send_proxy])
+
+ self.cli_set(back_base + [bk_second_name, 'mode', mode])
+ self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'address', bk_server_second])
+ self.cli_set(back_base + [bk_second_name, 'server', bk_second_name, 'port', bk_server_port])
+
+ self.cli_set(base_path + ['global-parameters', 'max-connections', max_connections])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(HAPROXY_CONF)
+
+ # Global
+ self.assertIn(f'maxconn {max_connections}', config)
+
+ # Frontend
+ self.assertIn(f'frontend {frontend}', config)
+ self.assertIn(f'bind :::{front_port} v4v6', config)
+ self.assertIn(f'mode {mode}', config)
+ for domain in domains_bk_first:
+ self.assertIn(f'acl {rule_ten} hdr(host) -i {domain}', config)
+ self.assertIn(f'use_backend {bk_first_name} if {rule_ten}', config)
+ self.assertIn(f'acl {rule_twenty} hdr(host) -i {domain_bk_second}', config)
+ self.assertIn(f'use_backend {bk_second_name} if {rule_twenty}', config)
+
+ # Backend
+ self.assertIn(f'backend {bk_first_name}', config)
+ self.assertIn(f'balance roundrobin', config)
+ self.assertIn(f'option forwardfor', config)
+ self.assertIn('http-request add-header X-Forwarded-Proto https if { ssl_fc }', config)
+ self.assertIn(f'mode {mode}', config)
+ self.assertIn(f'server {bk_first_name} {bk_server_first}:{bk_server_port} send-proxy', config)
+
+ self.assertIn(f'backend {bk_second_name}', config)
+ self.assertIn(f'mode {mode}', config)
+ self.assertIn(f'server {bk_second_name} {bk_server_second}:{bk_server_port}', config)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 57705e26f..a3aa41f94 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -45,22 +45,32 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_dyndns_service(self):
+ from itertools import product
ddns = ['interface', interface, 'service']
+ users = [None, 'vyos_user']
services = ['cloudflare', 'afraid', 'dyndns', 'zoneedit']
- for service in services:
- user = 'vyos_user'
+ for user, service in product(users, services):
password = 'vyos_pass'
zone = 'vyos.io'
self.cli_delete(base_path)
self.cli_set(base_path + ddns + [service, 'host-name', hostname])
- self.cli_set(base_path + ddns + [service, 'login', user])
+ if user is not None:
+ self.cli_set(base_path + ddns + [service, 'login', user])
self.cli_set(base_path + ddns + [service, 'password', password])
self.cli_set(base_path + ddns + [service, 'zone', zone])
# commit changes
if service == 'cloudflare':
self.cli_commit()
+ elif user is None:
+ # not set user is only allowed for cloudflare
+ with self.assertRaises(ConfigSessionError):
+ # remove zone to test not set user
+ self.cli_delete(base_path + ddns + [service, 'zone', 'vyos.io'])
+ self.cli_commit()
+ # this case is fininshed, user not set is not allowed when service isn't cloudflare
+ continue
else:
# zone option only works on cloudflare, an exception is raised
# for all others
@@ -72,7 +82,7 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
# we can only read the configuration file when we operate as 'root'
protocol = get_config_value('protocol')
- login = get_config_value('login')
+ login = None if user is None else get_config_value('login')
pwd = get_config_value('password')
# some services need special treatment
diff --git a/src/conf_mode/dynamic_dns.py b/src/conf_mode/dynamic_dns.py
index 06a2f7e15..426e3d693 100755
--- a/src/conf_mode/dynamic_dns.py
+++ b/src/conf_mode/dynamic_dns.py
@@ -108,7 +108,8 @@ def verify(dyndns):
raise ConfigError(f'"host-name" {error_msg}')
if 'login' not in config:
- raise ConfigError(f'"login" (username) {error_msg}')
+ if service != 'cloudflare' and ('protocol' not in config or config['protocol'] != 'cloudflare'):
+ raise ConfigError(f'"login" (username) {error_msg}, unless using CloudFlare')
if 'password' not in config:
raise ConfigError(f'"password" {error_msg}')
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index 7a63f5b4b..e18b426b1 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -21,6 +21,7 @@ from ipaddress import ip_interface
from ipaddress import IPv4Interface
from ipaddress import IPv6Interface
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.ifconfig.vrrp import VRRP
@@ -107,11 +108,16 @@ def verify(ha):
raise ConfigError(f'Authentication requires both type and passwortd to be set in VRRP group "{group}"')
if 'health_check' in group_config:
+ health_check_types = ["script", "ping"]
from vyos.utils.dict import check_mutually_exclusive_options
try:
- check_mutually_exclusive_options(group_config["health_check"], ["script", "ping"], required=True)
+ check_mutually_exclusive_options(group_config["health_check"], health_check_types, required=True)
except ValueError as e:
- raise ConfigError(f'Health check config is incorrect in VRRP group "{group}": {e}')
+ Warning(f'Health check configuration for VRRP group "{group}" will remain unused ' \
+ f'until it has one of the following options: {health_check_types}')
+ # XXX: health check has default options so we need to remove it
+ # to avoid generating useless config statements in keepalived.conf
+ del group_config["health_check"]
# Keepalived doesn't allow mixing IPv4 and IPv6 in one group, so we mirror that restriction
# We also need to make sure VRID is not used twice on the same interface with the
diff --git a/src/conf_mode/interfaces-ethernet.py b/src/conf_mode/interfaces-ethernet.py
index b49c945cd..31cfab368 100755
--- a/src/conf_mode/interfaces-ethernet.py
+++ b/src/conf_mode/interfaces-ethernet.py
@@ -22,6 +22,7 @@ from sys import exit
from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_address
from vyos.configverify import verify_dhcpv6
from vyos.configverify import verify_eapol
@@ -66,11 +67,17 @@ def get_config(config=None):
get_first_key=True, no_tag_node_value_mangle=True)
base = ['interfaces', 'ethernet']
- _, ethernet = get_interface_dict(conf, base)
+ ifname, ethernet = get_interface_dict(conf, base)
if 'deleted' not in ethernet:
if pki: ethernet['pki'] = pki
+ tmp = is_node_changed(conf, base + [ifname, 'speed'])
+ if tmp: ethernet.update({'speed_duplex_changed': {}})
+
+ tmp = is_node_changed(conf, base + [ifname, 'duplex'])
+ if tmp: ethernet.update({'speed_duplex_changed': {}})
+
return ethernet
def verify(ethernet):
diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py
new file mode 100755
index 000000000..b29fdffc7
--- /dev/null
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -0,0 +1,181 @@
+#!/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 os
+
+from sys import exit
+from shutil import rmtree
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.util import call
+from vyos.util import check_port_availability
+from vyos.util import is_listen_port_bind_service
+from vyos.pki import wrap_certificate
+from vyos.pki import wrap_private_key
+from vyos.template import render
+from vyos.xml import defaults
+from vyos import ConfigError
+from vyos import airbag
+airbag.enable()
+
+load_balancing_dir = '/run/haproxy'
+load_balancing_conf_file = f'{load_balancing_dir}/haproxy.cfg'
+systemd_service = 'haproxy.service'
+systemd_override = r'/run/systemd/system/haproxy.service.d/10-override.conf'
+
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['load-balancing', 'reverse-proxy']
+ lb = conf.get_config_dict(base,
+ get_first_key=True,
+ key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True)
+
+ if lb:
+ lb['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
+
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = defaults(base)
+ if 'backend' in default_values:
+ del default_values['backend']
+ if lb:
+ lb = dict_merge(default_values, lb)
+
+ if 'backend' in lb:
+ for backend in lb['backend']:
+ default_balues_backend = defaults(base + ['backend'])
+ lb['backend'][backend] = dict_merge(default_balues_backend, lb['backend'][backend])
+
+ return lb
+
+
+def verify(lb):
+ if not lb:
+ return None
+
+ if 'backend' not in lb or 'service' not in lb:
+ raise ConfigError(f'"service" and "backend" must be configured!')
+
+ for front, front_config in lb['service'].items():
+ if 'port' not in front_config:
+ raise ConfigError(f'"{front} service port" must be configured!')
+
+ # Check if bind address:port are used by another service
+ tmp_address = front_config.get('address', '0.0.0.0')
+ tmp_port = front_config['port']
+ if check_port_availability(tmp_address, int(tmp_port), 'tcp') is not True and \
+ not is_listen_port_bind_service(int(tmp_port), 'haproxy'):
+ raise ConfigError(f'"TCP" port "{tmp_port}" is used by another service')
+
+ for back, back_config in lb['backend'].items():
+ if 'server' not in back_config:
+ raise ConfigError(f'"{back} server" must be configured!')
+ for bk_server, bk_server_conf in back_config['server'].items():
+ if 'address' not in bk_server_conf or 'port' not in bk_server_conf:
+ raise ConfigError(f'"backend {back} server {bk_server} address and port" must be configured!')
+
+ if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
+ raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
+
+def generate(lb):
+ if not lb:
+ # Delete /run/haproxy/haproxy.cfg
+ config_files = [load_balancing_conf_file, systemd_override]
+ for file in config_files:
+ if os.path.isfile(file):
+ os.unlink(file)
+ # Delete old directories
+ #if os.path.isdir(load_balancing_dir):
+ # rmtree(load_balancing_dir, ignore_errors=True)
+
+ return None
+
+ # Create load-balance dir
+ if not os.path.isdir(load_balancing_dir):
+ os.mkdir(load_balancing_dir)
+
+ # SSL Certificates for frontend
+ for front, front_config in lb['service'].items():
+ if 'ssl' in front_config:
+ cert_file_path = os.path.join(load_balancing_dir, 'cert.pem')
+ cert_key_path = os.path.join(load_balancing_dir, 'cert.pem.key')
+ ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')
+
+ if 'certificate' in front_config['ssl']:
+ #cert_file_path = os.path.join(load_balancing_dir, 'cert.pem')
+ #cert_key_path = os.path.join(load_balancing_dir, 'cert.key')
+ cert_name = front_config['ssl']['certificate']
+ pki_cert = lb['pki']['certificate'][cert_name]
+
+ with open(cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_cert['certificate']))
+
+ if 'private' in pki_cert and 'key' in pki_cert['private']:
+ with open(cert_key_path, 'w') as f:
+ f.write(wrap_private_key(pki_cert['private']['key']))
+
+ if 'ca_certificate' in front_config['ssl']:
+ ca_name = front_config['ssl']['ca_certificate']
+ pki_ca_cert = lb['pki']['ca'][ca_name]
+
+ with open(ca_cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_ca_cert['certificate']))
+
+ # SSL Certificates for backend
+ for back, back_config in lb['backend'].items():
+ if 'ssl' in back_config:
+ ca_cert_file_path = os.path.join(load_balancing_dir, 'ca.pem')
+
+ if 'ca_certificate' in back_config['ssl']:
+ ca_name = back_config['ssl']['ca_certificate']
+ pki_ca_cert = lb['pki']['ca'][ca_name]
+
+ with open(ca_cert_file_path, 'w') as f:
+ f.write(wrap_certificate(pki_ca_cert['certificate']))
+
+ render(load_balancing_conf_file, 'load-balancing/haproxy.cfg.j2', lb)
+ render(systemd_override, 'load-balancing/override_haproxy.conf.j2', lb)
+
+ return None
+
+
+def apply(lb):
+ call('systemctl daemon-reload')
+ if not lb:
+ call(f'systemctl stop {systemd_service}')
+ else:
+ call(f'systemctl reload-or-restart {systemd_service}')
+
+ return None
+
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index e646fb0ae..cf34bad2e 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -111,9 +111,9 @@ def verify(syslog):
def generate(syslog):
if not syslog:
if os.path.exists(rsyslog_conf):
- os.path.unlink(rsyslog_conf)
+ os.unlink(rsyslog_conf)
if os.path.exists(logrotate_conf):
- os.path.unlink(logrotate_conf)
+ os.unlink(logrotate_conf)
return None
@@ -126,9 +126,10 @@ def generate(syslog):
return None
def apply(syslog):
+ systemd_socket = 'syslog.socket'
systemd_service = 'syslog.service'
if not syslog:
- call(f'systemctl stop {systemd_service}')
+ call(f'systemctl stop {systemd_service} {systemd_socket}')
return None
# we need to restart the service if e.g. the VRF name changed
diff --git a/src/conf_mode/vpn_openconnect.py b/src/conf_mode/vpn_openconnect.py
index 68da70d7d..83021a3e6 100755
--- a/src/conf_mode/vpn_openconnect.py
+++ b/src/conf_mode/vpn_openconnect.py
@@ -17,6 +17,7 @@
import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.pki import wrap_certificate
@@ -173,6 +174,19 @@ def verify(ocserv):
users_wo_pswd.append(user)
if users_wo_pswd:
raise ConfigError(f'password required for users:\n{users_wo_pswd}')
+
+ # Validate that if identity-based-config is configured all child config nodes are set
+ if 'identity_based_config' in ocserv["authentication"]:
+ if 'disabled' not in ocserv["authentication"]["identity_based_config"]:
+ Warning("Identity based configuration files is a 3rd party addition. Use at your own risk, this might break the ocserv daemon!")
+ if 'mode' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect radius identity-based-config enabled but mode not selected')
+ elif 'group' in ocserv["authentication"]["identity_based_config"]["mode"] and "radius" not in ocserv["authentication"]["mode"]:
+ raise ConfigError('OpenConnect config-per-group must be used with radius authentication')
+ if 'directory' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect identity-based-config enabled but directory not set')
+ if 'default_config' not in ocserv["authentication"]["identity_based_config"]:
+ raise ConfigError('OpenConnect identity-based-config enabled but default-config not set')
else:
raise ConfigError('openconnect authentication mode required')
else:
diff --git a/src/op_mode/interfaces.py b/src/op_mode/interfaces.py
index dd87b5901..f38b95a71 100755
--- a/src/op_mode/interfaces.py
+++ b/src/op_mode/interfaces.py
@@ -277,6 +277,10 @@ def _get_counter_data(ifname: typing.Optional[str],
res_intf['rx_bytes'] = _get_counter_val(cache['rx_bytes'], stats['rx_bytes'])
res_intf['tx_packets'] = _get_counter_val(cache['tx_packets'], stats['tx_packets'])
res_intf['tx_bytes'] = _get_counter_val(cache['tx_bytes'], stats['tx_bytes'])
+ res_intf['rx_dropped'] = _get_counter_val(cache['rx_dropped'], stats['rx_dropped'])
+ res_intf['tx_dropped'] = _get_counter_val(cache['tx_dropped'], stats['tx_dropped'])
+ res_intf['rx_over_errors'] = _get_counter_val(cache['rx_over_errors'], stats['rx_over_errors'])
+ res_intf['tx_carrier_errors'] = _get_counter_val(cache['tx_carrier_errors'], stats['tx_carrier_errors'])
ret.append(res_intf)
@@ -368,19 +372,23 @@ def _format_show_summary(data):
@catch_broken_pipe
def _format_show_counters(data: list):
- formatting = '%-12s %10s %10s %10s %10s'
- print(formatting % ('Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes'))
-
- for intf in data:
- print(formatting % (
- intf['ifname'],
- intf['rx_packets'],
- intf['rx_bytes'],
- intf['tx_packets'],
- intf['tx_bytes']
- ))
-
- return 0
+ data_entries = []
+ for entry in data:
+ interface = entry.get('ifname')
+ rx_packets = entry.get('rx_packets')
+ rx_bytes = entry.get('rx_bytes')
+ tx_packets = entry.get('tx_packets')
+ tx_bytes = entry.get('tx_bytes')
+ rx_dropped = entry.get('rx_dropped')
+ tx_dropped = entry.get('tx_dropped')
+ rx_errors = entry.get('rx_over_errors')
+ tx_errors = entry.get('tx_carrier_errors')
+ data_entries.append([interface, rx_packets, rx_bytes, tx_packets, tx_bytes, rx_dropped, tx_dropped, rx_errors, tx_errors])
+
+ headers = ['Interface', 'Rx Packets', 'Rx Bytes', 'Tx Packets', 'Tx Bytes', 'Rx Dropped', 'Tx Dropped', 'Rx Errors', 'Tx Errors']
+ output = tabulate(data_entries, headers, numalign="left")
+ print (output)
+ return output
def show(raw: bool, intf_name: typing.Optional[str],
intf_type: typing.Optional[str],
diff --git a/src/tests/test_util.py b/src/tests/test_util.py
index d8b2b7940..473052bef 100644
--- a/src/tests/test_util.py
+++ b/src/tests/test_util.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
@@ -26,17 +26,3 @@ class TestVyOSUtil(TestCase):
def test_sysctl_read(self):
self.assertEqual(sysctl_read('net.ipv4.conf.lo.forwarding'), '1')
-
- def test_camel_to_snake_case(self):
- self.assertEqual(camel_to_snake_case('ConnectionTimeout'),
- 'connection_timeout')
- self.assertEqual(camel_to_snake_case('connectionTimeout'),
- 'connection_timeout')
- self.assertEqual(camel_to_snake_case('TCPConnectionTimeout'),
- 'tcp_connection_timeout')
- self.assertEqual(camel_to_snake_case('TCPPort'),
- 'tcp_port')
- self.assertEqual(camel_to_snake_case('UseHTTPProxy'),
- 'use_http_proxy')
- self.assertEqual(camel_to_snake_case('CustomerID'),
- 'customer_id')