summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/configd-include.json1
-rw-r--r--data/templates/frr/bgpd.frr.j240
-rw-r--r--data/templates/frr/daemons.frr.tmpl3
-rw-r--r--data/templates/frr/zebra.segment_routing.frr.j223
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j213
-rw-r--r--data/templates/ocserv/ocserv_config.j25
-rw-r--r--debian/control2
-rw-r--r--interface-definitions/dns-dynamic.xml.in4
-rw-r--r--interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i14
-rw-r--r--interface-definitions/include/bgp/protocol-common-config.xml.i146
-rw-r--r--interface-definitions/include/constraint/alpha-numeric-hyphen-underscore.xml.i2
-rw-r--r--interface-definitions/include/constraint/dhcp-client-string-option.xml.i2
-rw-r--r--interface-definitions/include/firewall/action.xml.i8
-rw-r--r--interface-definitions/include/firewall/ipv4-custom-name.xml.i1
-rw-r--r--interface-definitions/include/firewall/ipv6-custom-name.xml.i1
-rw-r--r--interface-definitions/include/pki/certificate-multi.xml.i15
-rw-r--r--interface-definitions/load-balancing-haproxy.xml.in2
-rw-r--r--interface-definitions/protocols-segment-routing.xml.in89
-rw-r--r--interface-definitions/system-frr.xml.in14
-rw-r--r--interface-definitions/vpn-openconnect.xml.in6
-rw-r--r--op-mode-definitions/show-bgp.xml.in13
-rw-r--r--op-mode-definitions/show-segment-routing.xml.in27
-rw-r--r--python/vyos/config_mgmt.py11
-rw-r--r--python/vyos/ifconfig/interface.py2
-rw-r--r--python/vyos/kea.py17
-rw-r--r--python/vyos/remote.py2
-rw-r--r--python/vyos/system/disk.py5
-rw-r--r--python/vyos/system/grub.py2
-rw-r--r--python/vyos/system/raid.py31
-rw-r--r--python/vyos/utils/file.py4
-rw-r--r--python/vyos/utils/io.py9
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py36
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py71
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_segment_routing.py70
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py39
-rwxr-xr-xsmoketest/scripts/cli/test_system_frr.py26
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py21
-rwxr-xr-xsrc/conf_mode/dhcp_server.py11
-rwxr-xr-xsrc/conf_mode/dhcpv6_server.py11
-rwxr-xr-xsrc/conf_mode/dns_dynamic.py24
-rwxr-xr-xsrc/conf_mode/load-balancing-haproxy.py20
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py24
-rwxr-xr-xsrc/conf_mode/protocols_segment_routing.py74
-rwxr-xr-xsrc/conf_mode/system_frr.py4
-rwxr-xr-xsrc/helpers/simple-download.py20
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/0-to-129
-rwxr-xr-xsrc/migration-scripts/dns-dynamic/2-to-345
-rwxr-xr-xsrc/migration-scripts/nat/6-to-78
-rwxr-xr-xsrc/op_mode/dhcp.py10
-rwxr-xr-xsrc/op_mode/image_installer.py58
-rwxr-xr-xsrc/system/on-dhcp-event.sh24
-rwxr-xr-xsrc/validators/bgp-large-community-list8
52 files changed, 995 insertions, 152 deletions
diff --git a/data/configd-include.json b/data/configd-include.json
index a762a6d4c..92d3863ce 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -53,6 +53,7 @@
"protocols_rip.py",
"protocols_ripng.py",
"protocols_rpki.py",
+"protocols_segment_routing.py",
"protocols_static.py",
"protocols_static_multicast.py",
"qos.py",
diff --git a/data/templates/frr/bgpd.frr.j2 b/data/templates/frr/bgpd.frr.j2
index 6f81174ac..679ba8b04 100644
--- a/data/templates/frr/bgpd.frr.j2
+++ b/data/templates/frr/bgpd.frr.j2
@@ -470,6 +470,38 @@ router bgp {{ system_as }} {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% endfor %}
{% endif %}
!
+{% if bmp is vyos_defined %}
+{% if bmp.mirror_buffer_limit is vyos_defined %}
+ bmp mirror buffer-limit {{ bmp.mirror_buffer_limit }}
+ !
+{% endif %}
+{% if bmp.target is vyos_defined %}
+{% for bmp, bmp_config in bmp.target.items() %}
+ bmp targets {{ bmp }}
+{% if bmp_config.mirror is vyos_defined %}
+ bmp mirror
+{% endif %}
+{% if bmp_config.monitor is vyos_defined %}
+{% if bmp_config.monitor.ipv4_unicast.pre_policy is vyos_defined %}
+ bmp monitor ipv4 unicast pre-policy
+{% endif %}
+{% if bmp_config.monitor.ipv4_unicast.post_policy is vyos_defined %}
+ bmp monitor ipv4 unicast post-policy
+{% endif %}
+{% if bmp_config.monitor.ipv6_unicast.pre_policy is vyos_defined %}
+ bmp monitor ipv6 unicast pre-policy
+{% endif %}
+{% if bmp_config.monitor.ipv6_unicast.post_policy is vyos_defined %}
+ bmp monitor ipv6 unicast post-policy
+{% endif %}
+{% endif %}
+{% if bmp_config.address is vyos_defined %}
+ bmp connect {{ bmp_config.address }} port {{ bmp_config.port }} min-retry {{ bmp_config.min_retry }} max-retry {{ bmp_config.max_retry }}
+{% endif %}
+{% endfor %}
+ exit
+{% endif %}
+{% endif %}
{% if peer_group is vyos_defined %}
{% for peer, config in peer_group.items() %}
{{ bgp_neighbor(peer, config, true) }}
@@ -588,6 +620,14 @@ bgp route-reflector allow-outbound-policy
{% if parameters.tcp_keepalive.idle is vyos_defined and parameters.tcp_keepalive.interval is vyos_defined and parameters.tcp_keepalive.probes is vyos_defined %}
bgp tcp-keepalive {{ parameters.tcp_keepalive.idle }} {{ parameters.tcp_keepalive.interval }} {{ parameters.tcp_keepalive.probes }}
{% endif %}
+{% if srv6.locator is vyos_defined %}
+ segment-routing srv6
+ locator {{ srv6.locator }}
+ exit
+{% endif %}
+{% if sid.vpn.per_vrf.export is vyos_defined %}
+ sid vpn per-vrf export {{ sid.vpn.per_vrf.export }}
+{% endif %}
{% if timers.keepalive is vyos_defined and timers.holdtime is vyos_defined %}
timers bgp {{ timers.keepalive }} {{ timers.holdtime }}
{% endif %}
diff --git a/data/templates/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index a65f0868a..c637e18bc 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -108,7 +108,6 @@ valgrind_enable=no
frr_profile="traditional"
-#MAX_FDS=1024
+MAX_FDS={{ descriptors }}
#FRR_NO_ROOT="yes"
-
diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2
new file mode 100644
index 000000000..7b12fcdd0
--- /dev/null
+++ b/data/templates/frr/zebra.segment_routing.frr.j2
@@ -0,0 +1,23 @@
+!
+{% if srv6.locator is vyos_defined %}
+segment-routing
+ srv6
+ locators
+{% for locator, locator_config in srv6.locator.items() %}
+ locator {{ locator }}
+{% if locator_config.prefix is vyos_defined %}
+ prefix {{ locator_config.prefix }} block-len {{ locator_config.block_len }} node-len {{ locator_config.node_len }} func-bits {{ locator_config.func_bits }}
+{% endif %}
+{% if locator_config.behavior_usid is vyos_defined %}
+ behavior usid
+{% endif %}
+ exit
+ !
+{% endfor %}
+ exit
+ !
+exit
+!
+exit
+!
+{% endif %}
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index a75ee9904..defb76fba 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -50,13 +50,19 @@ defaults
{% 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 '' %}
+{% set ssl_front = [] %}
+{% if front_config.ssl.certificate is vyos_defined and front_config.ssl.certificate is iterable %}
+{% for cert in front_config.ssl.certificate %}
+{% set _ = ssl_front.append('crt /run/haproxy/' ~ cert ~ '.pem') %}
+{% endfor %}
+{% endif %}
+{% set ssl_directive = 'ssl' if ssl_front 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 }}
+ bind {{ address | bracketize_ipv6 }}:{{ front_config.port }} {{ ssl_directive }} {{ ssl_front | join(' ') }}
{% endfor %}
{% else %}
- bind :::{{ front_config.port }} v4v6 {{ ssl_front }}
+ bind :::{{ front_config.port }} v4v6 {{ ssl_directive }} {{ ssl_front | join(' ') }}
{% endif %}
{% if front_config.redirect_http_to_https is vyos_defined %}
http-request redirect scheme https unless { ssl_fc }
@@ -161,4 +167,3 @@ backend {{ back }}
{% endfor %}
{% endif %}
-
diff --git a/data/templates/ocserv/ocserv_config.j2 b/data/templates/ocserv/ocserv_config.j2
index 80ba357bc..b5e890c32 100644
--- a/data/templates/ocserv/ocserv_config.j2
+++ b/data/templates/ocserv/ocserv_config.j2
@@ -121,12 +121,12 @@ select-group = {{ grp }}
{% endfor %}
{% endif %}
-
+{% if http_security_headers is vyos_defined %}
# 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 = 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"
@@ -136,3 +136,4 @@ 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
+{% endif %}
diff --git a/debian/control b/debian/control
index 816d41944..08adc8a68 100644
--- a/debian/control
+++ b/debian/control
@@ -279,6 +279,8 @@ Depends:
# For "run monitor traffic"
tcpdump,
# End "run monitor traffic"
+# For "show hardware dmi"
+ dmidecode,
# For "run show hardware storage smart"
smartmontools,
# For "run show hardware scsi"
diff --git a/interface-definitions/dns-dynamic.xml.in b/interface-definitions/dns-dynamic.xml.in
index f089f0e52..388e7c5d2 100644
--- a/interface-definitions/dns-dynamic.xml.in
+++ b/interface-definitions/dns-dynamic.xml.in
@@ -19,6 +19,10 @@
<format>txt</format>
<description>Dynamic DNS service name</description>
</valueHelp>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ <constraintErrorMessage>Dynamic DNS service name must be alphanumeric and can contain hyphens and underscores</constraintErrorMessage>
</properties>
<children>
#include <include/generic-description.xml.i>
diff --git a/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i
new file mode 100644
index 000000000..261d60232
--- /dev/null
+++ b/interface-definitions/include/bgp/bmp-monitor-afi-policy.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from bgp/bmp-monitor-afi-policy.xml.i -->
+<leafNode name="pre-policy">
+ <properties>
+ <help>Send state before policy and filter processing</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<leafNode name="post-policy">
+ <properties>
+ <help>Send state with policy and filters applied</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i
index 4e43298bc..dce61ee77 100644
--- a/interface-definitions/include/bgp/protocol-common-config.xml.i
+++ b/interface-definitions/include/bgp/protocol-common-config.xml.i
@@ -909,6 +909,92 @@
</node>
</children>
</node>
+<node name="bmp">
+ <properties>
+ <help>BGP Monitoring Protocol (BMP)</help>
+ </properties>
+ <children>
+ <leafNode name="mirror-buffer-limit">
+ <properties>
+ <help>Maximum memory used for buffered mirroring messages (in bytes)</help>
+ <valueHelp>
+ <format>u32:0-4294967294</format>
+ <description>Limit in bytes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967294"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <tagNode name="target">
+ <properties>
+ <help>BMP target</help>
+ </properties>
+ <children>
+ #include <include/address-ipv4-ipv6-single.xml.i>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>5000</defaultValue>
+ </leafNode>
+ <leafNode name="min-retry">
+ <properties>
+ <help>Minimum connection retry interval (in milliseconds)</help>
+ <valueHelp>
+ <format>u32:100-86400000</format>
+ <description>Minimum connection retry interval</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 100-86400000"/>
+ </constraint>
+ </properties>
+ <defaultValue>1000</defaultValue>
+ </leafNode>
+ <leafNode name="max-retry">
+ <properties>
+ <help>Maximum connection retry interval</help>
+ <valueHelp>
+ <format>u32:100-4294967295</format>
+ <description>Maximum connection retry interval</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 100-86400000"/>
+ </constraint>
+ </properties>
+ <defaultValue>2000</defaultValue>
+ </leafNode>
+ <leafNode name="mirror">
+ <properties>
+ <help>Send BMP route mirroring messages</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="monitor">
+ <properties>
+ <help>Send BMP route monitoring messages</help>
+ </properties>
+ <children>
+ <node name="ipv4-unicast">
+ <properties>
+ <help>Address family IPv4 unicast</help>
+ </properties>
+ <children>
+ #include <include/bgp/bmp-monitor-afi-policy.xml.i>
+ </children>
+ </node>
+ <node name="ipv6-unicast">
+ <properties>
+ <help>Address family IPv6 unicast</help>
+ </properties>
+ <children>
+ #include <include/bgp/bmp-monitor-afi-policy.xml.i>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </tagNode>
+ </children>
+</node>
<tagNode name="interface">
<properties>
<help>Configure interface related parameters, e.g. MPLS</help>
@@ -1639,6 +1725,66 @@
#include <include/port-number.xml.i>
</children>
</tagNode>
+<node name="srv6">
+ <properties>
+ <help>Segment-Routing SRv6 configuration</help>
+ </properties>
+ <children>
+ <leafNode name="locator">
+ <properties>
+ <help>Specify SRv6 locator</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>SRv6 locator name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</node>
+<node name="sid">
+ <properties>
+ <help>SID value for VRF</help>
+ </properties>
+ <children>
+ <node name="vpn">
+ <properties>
+ <help>Between current VRF and VPN</help>
+ </properties>
+ <children>
+ <node name="per-vrf">
+ <properties>
+ <help>SID per-VRF (both IPv4 and IPv6 address families)</help>
+ </properties>
+ <children>
+ <leafNode name="export">
+ <properties>
+ <help>For routes leaked from current VRF to VPN</help>
+ <completionHelp>
+ <list>auto</list>
+ </completionHelp>
+ <valueHelp>
+ <format>u32:1-1048575</format>
+ <description>SID allocation index</description>
+ </valueHelp>
+ <valueHelp>
+ <format>auto</format>
+ <description>Automatically assign a label</description>
+ </valueHelp>
+ <constraint>
+ <regex>auto</regex>
+ <validator name="numeric" argument="--range 1-1048575"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+</node>
<node name="timers">
<properties>
<help>BGP protocol timers</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 ba097c6b5..399f2e1da 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.i -->
+<!-- include start from constraint/alpha-numeric-hyphen-underscore.xml.i -->
<regex>[-_a-zA-Z0-9]+</regex>
<!-- include end -->
diff --git a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
index 76e0e5466..88257a9bb 100644
--- a/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
+++ b/interface-definitions/include/constraint/dhcp-client-string-option.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from include/constraint/dhcp-client-string-option.xml.i -->
+<!-- include start from constraint/dhcp-client-string-option.xml.i -->
<regex>[-_a-zA-Z0-9\s]+</regex>
<regex>([a-fA-F0-9][a-fA-F0-9]:){2,}[a-fA-F0-9][a-fA-F0-9]</regex>
<!-- include end -->
diff --git a/interface-definitions/include/firewall/action.xml.i b/interface-definitions/include/firewall/action.xml.i
index 954e4f23e..e1f0c6cb6 100644
--- a/interface-definitions/include/firewall/action.xml.i
+++ b/interface-definitions/include/firewall/action.xml.i
@@ -3,7 +3,7 @@
<properties>
<help>Rule action</help>
<completionHelp>
- <list>accept continue jump reject return drop queue synproxy</list>
+ <list>accept continue jump reject return drop queue offload synproxy</list>
</completionHelp>
<valueHelp>
<format>accept</format>
@@ -34,11 +34,15 @@
<description>Enqueue packet to userspace</description>
</valueHelp>
<valueHelp>
+ <format>offload</format>
+ <description>Offload packet via flowtable</description>
+ </valueHelp>
+ <valueHelp>
<format>synproxy</format>
<description>Synproxy connections</description>
</valueHelp>
<constraint>
- <regex>(accept|continue|jump|reject|return|drop|queue|synproxy)</regex>
+ <regex>(accept|continue|jump|reject|return|drop|queue|offload|synproxy)</regex>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/include/firewall/ipv4-custom-name.xml.i b/interface-definitions/include/firewall/ipv4-custom-name.xml.i
index 9d6ecfaf2..c6420fe1f 100644
--- a/interface-definitions/include/firewall/ipv4-custom-name.xml.i
+++ b/interface-definitions/include/firewall/ipv4-custom-name.xml.i
@@ -33,6 +33,7 @@
<children>
#include <include/firewall/common-rule-ipv4.xml.i>
#include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/offload-target.xml.i>
#include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/firewall/ipv6-custom-name.xml.i b/interface-definitions/include/firewall/ipv6-custom-name.xml.i
index 81610babf..2cc45a60c 100644
--- a/interface-definitions/include/firewall/ipv6-custom-name.xml.i
+++ b/interface-definitions/include/firewall/ipv6-custom-name.xml.i
@@ -33,6 +33,7 @@
<children>
#include <include/firewall/common-rule-ipv6.xml.i>
#include <include/firewall/inbound-interface.xml.i>
+ #include <include/firewall/offload-target.xml.i>
#include <include/firewall/outbound-interface.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/pki/certificate-multi.xml.i b/interface-definitions/include/pki/certificate-multi.xml.i
new file mode 100644
index 000000000..c49c5d9b2
--- /dev/null
+++ b/interface-definitions/include/pki/certificate-multi.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from pki/certificate-multi.xml.i -->
+<leafNode name="certificate">
+ <properties>
+ <help>Certificate in PKI configuration</help>
+ <completionHelp>
+ <path>pki certificate</path>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Name of certificate in PKI configuration</description>
+ </valueHelp>
+ <multi/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/load-balancing-haproxy.xml.in b/interface-definitions/load-balancing-haproxy.xml.in
index 564c335ec..8f6bd3a99 100644
--- a/interface-definitions/load-balancing-haproxy.xml.in
+++ b/interface-definitions/load-balancing-haproxy.xml.in
@@ -49,7 +49,7 @@
<help>SSL Certificate, SSL Key and CA</help>
</properties>
<children>
- #include <include/pki/certificate.xml.i>
+ #include <include/pki/certificate-multi.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/protocols-segment-routing.xml.in b/interface-definitions/protocols-segment-routing.xml.in
new file mode 100644
index 000000000..d461e9c5d
--- /dev/null
+++ b/interface-definitions/protocols-segment-routing.xml.in
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="segment-routing" owner="${vyos_conf_scripts_dir}/protocols_segment_routing.py">
+ <properties>
+ <help>Segment Routing</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <node name="srv6">
+ <properties>
+ <help>Segment-Routing SRv6 configuration</help>
+ </properties>
+ <children>
+ <tagNode name="locator">
+ <properties>
+ <help>Segment Routing SRv6 locator</help>
+ <constraint>
+ #include <include/constraint/alpha-numeric-hyphen-underscore.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="behavior-usid">
+ <properties>
+ <help>Set SRv6 behavior uSID</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="prefix">
+ <properties>
+ <help>SRv6 locator prefix</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>SRv6 locator prefix</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="block-len">
+ <properties>
+ <help>Configure SRv6 locator block length in bits</help>
+ <valueHelp>
+ <format>u32:16-64</format>
+ <description>Specify SRv6 locator block length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 16-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>40</defaultValue>
+ </leafNode>
+ <leafNode name="func-bits">
+ <properties>
+ <help>Configure SRv6 locator function length in bits</help>
+ <valueHelp>
+ <format>u32:0-64</format>
+ <description>Specify SRv6 locator function length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>16</defaultValue>
+ </leafNode>
+ <leafNode name="node-len">
+ <properties>
+ <help>Configure SRv6 locator node length in bits</help>
+ <valueHelp>
+ <format>u32:16-64</format>
+ <description>Configure SRv6 locator node length in bits</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 16-64"/>
+ </constraint>
+ </properties>
+ <defaultValue>24</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/interface-definitions/system-frr.xml.in b/interface-definitions/system-frr.xml.in
index 9fe23ed75..76001b392 100644
--- a/interface-definitions/system-frr.xml.in
+++ b/interface-definitions/system-frr.xml.in
@@ -15,6 +15,20 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="descriptors">
+ <properties>
+ <help>Number of open file descriptors a process is allowed to use</help>
+ <valueHelp>
+ <format>u32:1024-8192</format>
+ <description>Number of file descriptors</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1024-8192"/>
+ </constraint>
+ <constraintErrorMessage>Port number must be in range 1024 to 8192</constraintErrorMessage>
+ </properties>
+ <defaultValue>1024</defaultValue>
+ </leafNode>
<leafNode name="irdp">
<properties>
<help>Enable ICMP Router Discovery Protocol support</help>
diff --git a/interface-definitions/vpn-openconnect.xml.in b/interface-definitions/vpn-openconnect.xml.in
index 75c64a99a..736084f8b 100644
--- a/interface-definitions/vpn-openconnect.xml.in
+++ b/interface-definitions/vpn-openconnect.xml.in
@@ -260,6 +260,12 @@
</leafNode>
</children>
</node>
+ <leafNode name="http-security-headers">
+ <properties>
+ <help>Enable HTTP security headers</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<node name="ssl">
<properties>
<help>SSL Certificate, SSL Key and CA</help>
diff --git a/op-mode-definitions/show-bgp.xml.in b/op-mode-definitions/show-bgp.xml.in
index 3c212614c..8b1992432 100644
--- a/op-mode-definitions/show-bgp.xml.in
+++ b/op-mode-definitions/show-bgp.xml.in
@@ -100,6 +100,19 @@
</children>
</tagNode>
#include <include/vtysh-generic-wide.xml.i>
+ <node name="segment-routing">
+ <properties>
+ <help>BGP Segment Routing</help>
+ </properties>
+ <children>
+ <leafNode name="srv6">
+ <properties>
+ <help>BGP Segment Routing SRv6</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/show-segment-routing.xml.in b/op-mode-definitions/show-segment-routing.xml.in
new file mode 100644
index 000000000..ebdb51a61
--- /dev/null
+++ b/op-mode-definitions/show-segment-routing.xml.in
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="segment-routing">
+ <properties>
+ <help>Show Segment Routing</help>
+ </properties>
+ <children>
+ <node name="srv6">
+ <properties>
+ <help>Segment Routing SRv6</help>
+ </properties>
+ <children>
+ <node name="locator">
+ <properties>
+ <help>Locator Information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index 2de3d1831..ff078649d 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -404,15 +404,8 @@ Proceed ?'''
_, _, netloc = url.netloc.rpartition("@")
redacted_location = urlunsplit(url._replace(netloc=netloc))
print(f" {redacted_location}", end=" ", flush=True)
- try:
- upload(archive_config_file, f'{location}/{remote_file}',
- source_host=source_address, raise_error=True)
- print("OK")
- except Exception as e:
- print("FAILED!")
- print()
- print(indent(str(e), " > "))
- print()
+ upload(archive_config_file, f'{location}/{remote_file}',
+ source_host=source_address)
# op-mode functions
#
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 1586710db..56dcde214 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -115,7 +115,7 @@ class Interface(Control):
},
'vrf': {
'shellcmd': 'ip -json -detail link list dev {ifname}',
- 'format': lambda j: jmespath.search('[*].master | [0]', json.loads(j)),
+ 'format': lambda j: jmespath.search('[?linkinfo.info_slave_kind == `vrf`].master | [0]', json.loads(j)),
},
}
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index cb341e0f2..4a517da5f 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -23,7 +23,9 @@ from vyos.template import is_ipv6
from vyos.template import isc_static_route
from vyos.template import netmask_from_cidr
from vyos.utils.dict import dict_search_args
+from vyos.utils.file import file_permissions
from vyos.utils.file import read_file
+from vyos.utils.process import cmd
kea4_options = {
'name_server': 'domain-name-servers',
@@ -119,10 +121,14 @@ def kea_parse_subnet(subnet, config):
if 'disable' in host_config:
continue
- reservations.append({
- 'hw-address': host_config['mac_address'],
- 'ip-address': host_config['ip_address']
- })
+ obj = {
+ 'hw-address': host_config['mac_address']
+ }
+
+ if 'ip_address' in host_config:
+ obj['ip-address'] = host_config['ip_address']
+
+ reservations.append(obj)
out['reservations'] = reservations
unifi_controller = dict_search_args(config, 'vendor_option', 'ubiquiti', 'unifi_controller')
@@ -275,6 +281,9 @@ def _ctrl_socket_command(path, command, args=None):
if not os.path.exists(path):
return None
+ if file_permissions(path) != '0775':
+ cmd(f'sudo chmod 775 {path}')
+
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect(path)
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index fec44b571..b1efcd10b 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -452,7 +452,7 @@ def upload(local_path, urlstring, progressbar=False,
source_host='', source_port=0, timeout=10.0):
try:
progressbar = progressbar and is_interactive()
- urlc(urlstring, progressbar, source_host, source_port, timeout).upload(local_path)
+ urlc(urlstring, progressbar, False, source_host, source_port, timeout).upload(local_path)
except Exception as err:
print_error(f'Unable to upload "{urlstring}": {err}')
except KeyboardInterrupt:
diff --git a/python/vyos/system/disk.py b/python/vyos/system/disk.py
index f8e0fd1bf..b8a2c0f35 100644
--- a/python/vyos/system/disk.py
+++ b/python/vyos/system/disk.py
@@ -31,12 +31,17 @@ class DiskDetails:
def disk_cleanup(drive_path: str) -> None:
"""Clean up disk partition table (MBR and GPT)
+ Remove partition and device signatures.
Zeroize primary and secondary headers - first and last 17408 bytes
(512 bytes * 34 LBA) on a drive
Args:
drive_path (str): path to a drive that needs to be cleaned
"""
+ partitions: list[str] = partition_list(drive_path)
+ for partition in partitions:
+ run(f'wipefs -af {partition}')
+ run(f'wipefs -af {drive_path}')
run(f'sgdisk -Z {drive_path}')
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index 0ac16af9a..2692aaea1 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -138,6 +138,8 @@ def version_list(root_dir: str = '') -> list[str]:
versions_list: list[str] = []
for file in versions_files:
versions_list.append(file.stem)
+ versions_list.sort(reverse=True)
+
return versions_list
diff --git a/python/vyos/system/raid.py b/python/vyos/system/raid.py
index 13b99fa69..5b33d34da 100644
--- a/python/vyos/system/raid.py
+++ b/python/vyos/system/raid.py
@@ -19,7 +19,7 @@ from pathlib import Path
from shutil import copy
from dataclasses import dataclass
-from vyos.utils.process import cmd
+from vyos.utils.process import cmd, run
from vyos.system import disk
@@ -44,18 +44,11 @@ def raid_create(raid_members: list[str],
"""
raid_devices_num: int = len(raid_members)
raid_members_str: str = ' '.join(raid_members)
- if Path('/sys/firmware/efi').exists():
- for part in raid_members:
- drive: str = disk.partition_parent(part)
- command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
- cmd(command)
- else:
- for part in raid_members:
- drive: str = disk.partition_parent(part)
- command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
- cmd(command)
for part in raid_members:
- command: str = f'mdadm --zero-superblock {part}'
+ drive: str = disk.partition_parent(part)
+ # set partition type GUID for raid member; cf.
+ # https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs
+ command: str = f'sgdisk --typecode=3:A19D880F-05FC-4D3B-A006-743F0F84911E {drive}'
cmd(command)
command: str = f'mdadm --create /dev/{raid_name} -R --metadata=1.0 \
--raid-devices={raid_devices_num} --level={raid_level} \
@@ -72,6 +65,20 @@ def raid_create(raid_members: list[str],
return raid
+def clear():
+ """Deactivate all RAID arrays"""
+ command: str = 'mdadm --examine --scan'
+ raid_config = cmd(command)
+ if not raid_config:
+ return
+ command: str = 'mdadm --run /dev/md?*'
+ run(command)
+ command: str = 'mdadm --assemble --scan --auto=yes --symlink=no'
+ run(command)
+ command: str = 'mdadm --stop --scan'
+ run(command)
+
+
def update_initramfs() -> None:
"""Update initramfs"""
mdadm_script = '/etc/initramfs-tools/scripts/local-top/mdadm'
diff --git a/python/vyos/utils/file.py b/python/vyos/utils/file.py
index 2af87a0ca..70ac1753b 100644
--- a/python/vyos/utils/file.py
+++ b/python/vyos/utils/file.py
@@ -149,6 +149,10 @@ def chmod_775(path):
S_IROTH | S_IXOTH
chmod(path, bitmask)
+def file_permissions(path):
+ """ Return file permissions in string format, e.g '0755' """
+ return oct(os.stat(path).st_mode)[4:]
+
def makedir(path, user=None, group=None):
if os.path.exists(path):
return
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index 74099b502..0afaf695c 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -26,13 +26,18 @@ def print_error(str='', end='\n'):
sys.stderr.write(end)
sys.stderr.flush()
-def ask_input(question, default='', numeric_only=False, valid_responses=[]):
+def ask_input(question, default='', numeric_only=False, valid_responses=[],
+ no_echo=False):
+ from getpass import getpass
question_out = question
if default:
question_out += f' (Default: {default})'
response = ''
while True:
- response = input(question_out + ' ').strip()
+ if not no_echo:
+ response = input(question_out + ' ').strip()
+ else:
+ response = getpass(question_out + ' ').strip()
if not response and default:
return default
if numeric_only:
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 066ed707b..5cfddb269 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -753,5 +753,41 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
+ def test_zone_flow_offload(self):
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0'])
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'hardware'])
+
+ # QEMU virtual NIC does not support hw-tc-offload
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(['firewall', 'flowtable', 'smoketest', 'offload', 'software'])
+
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'action', 'offload'])
+ self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest'])
+
+ self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'action', 'offload'])
+ self.cli_set(['firewall', 'ipv6', 'name', 'smoketest', 'rule', '1', 'offload-target', 'smoketest'])
+
+ self.cli_commit()
+
+ nftables_search = [
+ ['chain NAME_smoketest'],
+ ['flow add @VYOS_FLOWTABLE_smoketest']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+
+ nftables_search = [
+ ['chain NAME6_smoketest'],
+ ['flow add @VYOS_FLOWTABLE_smoketest']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 vyos_filter')
+
+ # Check conntrack
+ self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
+ self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 71e2142f9..8102a3153 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -15,6 +15,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import unittest
+from subprocess import run
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -1133,5 +1134,75 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' mpls bgp forwarding', frrconfig)
self.cli_delete(['interfaces', 'ethernet', interface, 'vrf'])
+ def test_bgp_24_srv6_sid(self):
+ locator_name = 'VyOS_foo'
+ sid = 'auto'
+
+ self.cli_set(base_path + ['srv6', 'locator', locator_name])
+ self.cli_set(base_path + ['sid', 'vpn', 'per-vrf', 'export', sid])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'router bgp {ASN}', frrconfig)
+ self.assertIn(f' segment-routing srv6', frrconfig)
+ self.assertIn(f' locator {locator_name}', frrconfig)
+ self.assertIn(f' sid vpn per-vrf export {sid}', frrconfig)
+
+ def test_bgp_25_bmp(self):
+ target_name = 'instance-bmp'
+ target_address = '127.0.0.1'
+ target_port = '5000'
+ min_retry = '1024'
+ max_retry = '2048'
+ monitor_ipv4 = 'pre-policy'
+ monitor_ipv6 = 'pre-policy'
+ mirror_buffer = '32000000'
+ bmp_path = base_path + ['bmp']
+ target_path = bmp_path + ['target', target_name]
+ bgpd_bmp_pid = process_named_running('bgpd', 'bmp')
+ command = ['/opt/vyatta/bin/vyatta-op-cmd-wrapper', 'restart', 'bgp']
+
+ self.cli_set(bmp_path)
+ # by default the 'bmp' module not loaded for the bgpd
+ # expect Error
+ if not bgpd_bmp_pid:
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # add required 'bmp' module to bgpd and restart bgpd
+ self.cli_delete(bmp_path)
+ self.cli_set(['system', 'frr', 'bmp'])
+ self.cli_commit()
+ # restart bgpd to apply "-M bmp" and update PID
+ run(command, input='Y', text=True)
+ self.daemon_pid = process_named_running(PROCESS_NAME)
+
+ # set bmp config but not set address
+ self.cli_set(target_path + ['port', target_port])
+ # address is not set, expect Error
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # config other bmp options
+ self.cli_set(target_path + ['address', target_address])
+ self.cli_set(bmp_path + ['mirror-buffer-limit', mirror_buffer])
+ self.cli_set(target_path + ['port', target_port])
+ self.cli_set(target_path + ['min-retry', min_retry])
+ self.cli_set(target_path + ['max-retry', max_retry])
+ self.cli_set(target_path + ['mirror'])
+ self.cli_set(target_path + ['monitor', 'ipv4-unicast', monitor_ipv4])
+ self.cli_set(target_path + ['monitor', 'ipv6-unicast', monitor_ipv6])
+ self.cli_commit()
+
+ # Verify bgpd bmp configuration
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'bmp mirror buffer-limit {mirror_buffer}', frrconfig)
+ self.assertIn(f'bmp targets {target_name}', frrconfig)
+ self.assertIn(f'bmp mirror', frrconfig)
+ self.assertIn(f'bmp monitor ipv4 unicast {monitor_ipv4}', frrconfig)
+ self.assertIn(f'bmp monitor ipv6 unicast {monitor_ipv6}', frrconfig)
+ self.assertIn(f'bmp connect {target_address} port {target_port} min-retry {min_retry} max-retry {max_retry}', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_protocols_segment_routing.py b/smoketest/scripts/cli/test_protocols_segment_routing.py
new file mode 100755
index 000000000..81d42b925
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_segment_routing.py
@@ -0,0 +1,70 @@
+#!/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
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.utils.process import cmd
+from vyos.utils.process import process_named_running
+
+base_path = ['protocols', 'segment-routing']
+PROCESS_NAME = 'zebra'
+
+class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # call base-classes classmethod
+ super(TestProtocolsSegmentRouting, cls).setUpClass()
+ # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
+ cls.daemon_pid = process_named_running(PROCESS_NAME)
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # check process health and continuity
+ self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+
+ def test_srv6(self):
+ locators = {
+ 'foo' : { 'prefix' : '2001:a::/64' },
+ 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} },
+ }
+
+ for locator, locator_config in locators.items():
+ self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']])
+ if 'usid' in locator_config:
+ self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid'])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'segment-routing', daemon='zebra')
+ self.assertIn(f'segment-routing', frrconfig)
+ self.assertIn(f' srv6', frrconfig)
+ self.assertIn(f' locators', frrconfig)
+ for locator, locator_config in locators.items():
+ self.assertIn(f' locator {locator}', frrconfig)
+ self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig)
+
+
+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 3c7303f32..ae46b18ba 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -17,8 +17,6 @@
import os
import unittest
import tempfile
-import random
-import string
from base_vyostest_shim import VyOSUnitTestSHIM
@@ -67,14 +65,12 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
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(name_path + [svc, opt, value])
- # 'zone' option is supported and required by 'cloudfare', but not 'freedns' and 'zoneedit'
+ # 'zone' option is supported by 'cloudfare' and 'zoneedit1', but not 'freedns'
self.cli_set(name_path + [svc, 'zone', zone])
- if details['protocol'] == 'cloudflare':
+ if details['protocol'] in ['cloudflare', 'zoneedit1']:
pass
else:
# exception is raised for unsupported ones
@@ -292,7 +288,36 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'password=\'{password}\'', ddclient_conf)
self.assertIn(f'{hostname}', ddclient_conf)
- def test_07_dyndns_vrf(self):
+ def test_07_dyndns_dynamic_interface(self):
+ # Check if DDNS service can be configured and runs
+ svc_path = name_path + ['namecheap']
+ proto = 'namecheap'
+ dyn_interface = 'pppoe587'
+
+ self.cli_set(svc_path + ['address', dyn_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', hostname])
+
+ # Dynamic interface will raise a warning but still go through
+ # XXX: We should have idiomatic class "ConfigSessionWarning" wrapping
+ # "Warning" similar to "ConfigSessionError".
+ # with self.assertWarns(Warning):
+ # self.cli_commit()
+ self.cli_commit()
+
+ # Check the generating config parameters
+ ddclient_conf = cmd(f'sudo cat {DDCLIENT_CONF}')
+ self.assertIn(f'ifv4={dyn_interface}', ddclient_conf)
+ self.assertIn(f'protocol={proto}', ddclient_conf)
+ self.assertIn(f'server={server}', ddclient_conf)
+ self.assertIn(f'login={username}', ddclient_conf)
+ self.assertIn(f'password=\'{password}\'', ddclient_conf)
+ self.assertIn(f'{hostname}', ddclient_conf)
+
+ def test_08_dyndns_vrf(self):
# Table number randomized, but should be within range 100-65535
vrf_table = '58710'
vrf_name = f'vyos-test-{vrf_table}'
diff --git a/smoketest/scripts/cli/test_system_frr.py b/smoketest/scripts/cli/test_system_frr.py
index 3eb0cd0ab..a2ce58bf6 100755
--- a/smoketest/scripts/cli/test_system_frr.py
+++ b/smoketest/scripts/cli/test_system_frr.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2020 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
@@ -16,13 +16,13 @@
import re
import unittest
+
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.file import read_file
config_file = '/etc/frr/daemons'
base_path = ['system', 'frr']
-
def daemons_config_parse(daemons_config):
# create regex for parsing daemons options
regex_daemon_config = re.compile(
@@ -33,13 +33,20 @@ def daemons_config_parse(daemons_config):
for daemon in regex_daemon_config.finditer(daemons_config):
daemon_name = daemon.group('daemon_name')
daemon_options = daemon.group('daemon_options')
- daemons_config_dict[daemon_name] = daemon_options
+ daemons_config_dict[daemon_name] = daemon_options.lstrip()
# return daemons config
return (daemons_config_dict)
class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestSystemFRR, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
def tearDown(self):
self.cli_delete(base_path)
@@ -64,7 +71,7 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
else:
self.assertFalse(snmp_enabled)
- def test_frr_snmp_addandremove(self):
+ def test_frr_snmp_add_remove(self):
# test enabling and disabling of SNMP integration
test_daemon_names = ['ospfd', 'bgpd']
for test_daemon_name in test_daemon_names:
@@ -124,7 +131,7 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
irdp_enabled = regex_irdp.match(daemons_config_dict['zebra'])
self.assertTrue(irdp_enabled)
- def test_frr_bmpandsnmp(self):
+ def test_frr_bmp_and_snmp(self):
# test empty config section
self.cli_set(base_path + ['bmp'])
self.cli_set(base_path + ['snmp', 'bgpd'])
@@ -141,6 +148,15 @@ class TestSystemFRR(VyOSUnitTestSHIM.TestCase):
self.assertTrue(bmp_enabled)
self.assertTrue(snmp_enabled)
+ def test_frr_file_descriptors(self):
+ file_descriptors = '4096'
+
+ self.cli_set(base_path + ['descriptors', file_descriptors])
+ self.cli_commit()
+
+ # read the config file and check content
+ daemons_config = read_file(config_file)
+ self.assertIn(f'MAX_FDS={file_descriptors}', daemons_config)
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index 04abeb1aa..c4502fada 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -141,5 +141,26 @@ class TestVPNOpenConnect(VyOSUnitTestSHIM.TestCase):
otp_config = read_file(otp_file)
self.assertIn(f'HOTP/T30/6 {user} - {otp}', otp_config)
+
+ # Verify HTTP security headers
+ self.cli_set(base_path + ['http-security-headers'])
+ self.cli_commit()
+
+ daemon_config = read_file(config_file)
+
+ self.assertIn('included-http-headers = Strict-Transport-Security: max-age=31536000 ; includeSubDomains', daemon_config)
+ self.assertIn('included-http-headers = X-Frame-Options: deny', daemon_config)
+ self.assertIn('included-http-headers = X-Content-Type-Options: nosniff', daemon_config)
+ self.assertIn('included-http-headers = Content-Security-Policy: default-src "none"', daemon_config)
+ self.assertIn('included-http-headers = X-Permitted-Cross-Domain-Policies: none', daemon_config)
+ self.assertIn('included-http-headers = Referrer-Policy: no-referrer', daemon_config)
+ self.assertIn('included-http-headers = Clear-Site-Data: "cache","cookies","storage"', daemon_config)
+ self.assertIn('included-http-headers = Cross-Origin-Embedder-Policy: require-corp', daemon_config)
+ self.assertIn('included-http-headers = Cross-Origin-Opener-Policy: same-origin', daemon_config)
+ self.assertIn('included-http-headers = Cross-Origin-Resource-Policy: same-origin', daemon_config)
+ self.assertIn('included-http-headers = X-XSS-Protection: 0', daemon_config)
+ self.assertIn('included-http-headers = Pragma: no-cache', daemon_config)
+ self.assertIn('included-http-headers = Cache-control: no-store, no-cache', daemon_config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/dhcp_server.py b/src/conf_mode/dhcp_server.py
index 66f7c8057..958e90014 100755
--- a/src/conf_mode/dhcp_server.py
+++ b/src/conf_mode/dhcp_server.py
@@ -21,7 +21,6 @@ from ipaddress import ip_network
from netaddr import IPAddress
from netaddr import IPRange
from sys import exit
-from time import sleep
from vyos.config import Config
from vyos.pki import wrap_certificate
@@ -29,7 +28,6 @@ from vyos.pki import wrap_private_key
from vyos.template import render
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_search_args
-from vyos.utils.file import chmod_775
from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import run
@@ -362,15 +360,6 @@ def apply(dhcp):
call(f'systemctl {action} {service}.service')
- # op-mode needs ctrl socket permission change
- i = 0
- while not os.path.exists(ctrl_socket):
- if i > 15:
- break
- i += 1
- sleep(1)
- chmod_775(ctrl_socket)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dhcpv6_server.py b/src/conf_mode/dhcpv6_server.py
index 73a708ff5..b01f510e5 100755
--- a/src/conf_mode/dhcpv6_server.py
+++ b/src/conf_mode/dhcpv6_server.py
@@ -19,13 +19,11 @@ import os
from ipaddress import ip_address
from ipaddress import ip_network
from sys import exit
-from time import sleep
from vyos.config import Config
from vyos.template import render
from vyos.template import is_ipv6
from vyos.utils.process import call
-from vyos.utils.file import chmod_775
from vyos.utils.file import write_file
from vyos.utils.dict import dict_search
from vyos.utils.network import is_subnet_connected
@@ -197,15 +195,6 @@ def apply(dhcpv6):
call(f'systemctl restart {service_name}')
- # op-mode needs ctrl socket permission change
- i = 0
- while not os.path.exists(ctrl_socket):
- if i > 15:
- break
- i += 1
- sleep(1)
- chmod_775(ctrl_socket)
-
return None
if __name__ == '__main__':
diff --git a/src/conf_mode/dns_dynamic.py b/src/conf_mode/dns_dynamic.py
index c4dcb76ed..809c650d9 100755
--- a/src/conf_mode/dns_dynamic.py
+++ b/src/conf_mode/dns_dynamic.py
@@ -30,6 +30,9 @@ airbag.enable()
config_file = r'/run/ddclient/ddclient.conf'
systemd_override = r'/run/systemd/system/ddclient.service.d/override.conf'
+# Dynamic interfaces that might not exist when the configuration is loaded
+dynamic_interfaces = ('pppoe', 'sstpc')
+
# Protocols that require zone
zone_necessary = ['cloudflare', 'digitalocean', 'godaddy', 'hetzner', 'gandi',
'nfsn', 'nsupdate']
@@ -86,17 +89,19 @@ def verify(dyndns):
if field not in config:
raise ConfigError(f'"{field.replace("_", "-")}" {error_msg_req}')
- # If dyndns address is an interface, ensure that it exists
+ # If dyndns address is an interface, ensure
+ # that the interface exists (or just warn if dynamic interface)
# and that web-options are not set
if config['address'] != 'web':
# exclude check interface for dynamic interfaces
- interface_filter = ('pppoe', 'sstpc')
- if config['address'].startswith(interface_filter):
- Warning(f'interface {config["address"]} does not exist!')
+ if config['address'].startswith(dynamic_interfaces):
+ Warning(f'Interface "{config["address"]}" does not exist yet and cannot '
+ f'be used for Dynamic DNS service "{service}" until it is up!')
else:
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')
+ raise ConfigError(f'"web-options" is applicable only when using HTTP(S) '
+ f'web request to obtain the IP address')
# RFC2136 uses 'key' instead of 'password'
if config['protocol'] != 'nsupdate' and 'password' not in config:
@@ -124,13 +129,16 @@ def verify(dyndns):
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"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'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"]}"')
+ raise ConfigError(f'Both IPv4 and IPv6 at the same time {error_msg_uns} '
+ f'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}"')
+ raise ConfigError(f'"expiry-time" must be greater than "wait-time" for '
+ f'Dynamic DNS service "{service}"')
return None
diff --git a/src/conf_mode/load-balancing-haproxy.py b/src/conf_mode/load-balancing-haproxy.py
index ec4311bb5..333ebc66c 100755
--- a/src/conf_mode/load-balancing-haproxy.py
+++ b/src/conf_mode/load-balancing-haproxy.py
@@ -108,17 +108,19 @@ def generate(lb):
if 'ssl' in front_config:
if 'certificate' in front_config['ssl']:
- cert_name = front_config['ssl']['certificate']
- pki_cert = lb['pki']['certificate'][cert_name]
- cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
- cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
+ cert_names = front_config['ssl']['certificate']
- with open(cert_file_path, 'w') as f:
- f.write(wrap_certificate(pki_cert['certificate']))
+ for cert_name in cert_names:
+ pki_cert = lb['pki']['certificate'][cert_name]
+ cert_file_path = os.path.join(load_balancing_dir, f'{cert_name}.pem')
+ cert_key_path = os.path.join(load_balancing_dir, f'{cert_name}.pem.key')
- 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']))
+ 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']
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 00015023c..bf807fa5f 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -30,6 +30,7 @@ from vyos.template import render_to_string
from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import is_addr_assigned
+from vyos.utils.process import process_named_running
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -49,8 +50,13 @@ def get_config(config=None):
# eqivalent of the C foo ? 'a' : 'b' statement
base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
- bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True, no_tag_node_value_mangle=True)
+ bgp = conf.get_config_dict(
+ base,
+ key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True,
+ with_recursive_defaults=True,
+ )
bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
key_mangling=('-', '_'),
@@ -93,6 +99,7 @@ def get_config(config=None):
tmp = conf.get_config_dict(['policy'])
# Merge policy dict into "regular" config dict
bgp = dict_merge(tmp, bgp)
+
return bgp
@@ -246,6 +253,19 @@ def verify(bgp):
if 'system_as' not in bgp:
raise ConfigError('BGP system-as number must be defined!')
+ # Verify BMP
+ if 'bmp' in bgp:
+ # check bmp flag "bgpd -d -F traditional --daemon -A 127.0.0.1 -M rpki -M bmp"
+ if not process_named_running('bgpd', 'bmp'):
+ raise ConfigError(
+ f'"bmp" flag is not found in bgpd. Configure "set system frr bmp" and restart bgp process'
+ )
+ # check bmp target
+ if 'target' in bgp['bmp']:
+ for target, target_config in bgp['bmp']['target'].items():
+ if 'address' not in target_config:
+ raise ConfigError(f'BMP target "{target}" address must be defined!')
+
# Verify vrf on interface and bgp section
if 'interface' in bgp:
for interface in bgp['interface']:
diff --git a/src/conf_mode/protocols_segment_routing.py b/src/conf_mode/protocols_segment_routing.py
new file mode 100755
index 000000000..eb1653212
--- /dev/null
+++ b/src/conf_mode/protocols_segment_routing.py
@@ -0,0 +1,74 @@
+#!/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 vyos.config import Config
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base = ['protocols', 'segment-routing']
+ sr = conf.get_config_dict(base, 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.
+ sr = conf.merge_defaults(sr, recursive=True)
+
+ return sr
+
+def verify(static):
+ return None
+
+def generate(static):
+ if not static:
+ return None
+
+ static['new_frr_config'] = render_to_string('frr/zebra.segment_routing.frr.j2', static)
+ return None
+
+def apply(static):
+ zebra_daemon = 'zebra'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(r'^segment-routing')
+ if 'new_frr_config' in static:
+ frr_cfg.add_before(frr.default_add_before, static['new_frr_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
+ 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_frr.py b/src/conf_mode/system_frr.py
index 6727b63c2..07f291000 100755
--- a/src/conf_mode/system_frr.py
+++ b/src/conf_mode/system_frr.py
@@ -40,7 +40,9 @@ def get_config(config=None):
conf = Config()
base = ['system', 'frr']
- frr_config = conf.get_config_dict(base, get_first_key=True)
+ frr_config = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True,
+ with_recursive_defaults=True)
return frr_config
diff --git a/src/helpers/simple-download.py b/src/helpers/simple-download.py
new file mode 100755
index 000000000..501af75f5
--- /dev/null
+++ b/src/helpers/simple-download.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python3
+
+import sys
+from argparse import ArgumentParser
+from vyos.remote import download
+
+parser = ArgumentParser()
+parser.add_argument('--local-file', help='local file', required=True)
+parser.add_argument('--remote-path', help='remote path', required=True)
+
+args = parser.parse_args()
+
+try:
+ download(args.local_file, args.remote_path,
+ check_space=True, raise_error=True)
+except Exception as e:
+ print(e)
+ sys.exit(1)
+
+sys.exit()
diff --git a/src/migration-scripts/dns-dynamic/0-to-1 b/src/migration-scripts/dns-dynamic/0-to-1
index d80e8d44a..4f6083eab 100755
--- a/src/migration-scripts/dns-dynamic/0-to-1
+++ b/src/migration-scripts/dns-dynamic/0-to-1
@@ -81,20 +81,33 @@ for address in config.list_nodes(new_base_path):
config.rename(new_base_path + [address, 'service', svc_cfg, 'login'], 'username')
# Apply global 'ipv6-enable' to per <config> 'ip-version: ipv6'
if config.exists(new_base_path + [address, 'ipv6-enable']):
- config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'],
- value='ipv6', replace=False)
+ config.set(new_base_path + [address, 'service', svc_cfg, 'ip-version'], 'ipv6')
config.delete(new_base_path + [address, 'ipv6-enable'])
# Apply service protocol mapping upfront, they are not 'auto-detected' anymore
if svc_cfg in service_protocol_mapping:
config.set(new_base_path + [address, 'service', svc_cfg, 'protocol'],
- value=service_protocol_mapping.get(svc_cfg), replace=False)
+ service_protocol_mapping.get(svc_cfg))
- # Migrate "service dns dynamic interface <interface> use-web"
- # to "service dns dynamic address <address> web-options"
- # Also, rename <address> to 'web' literal for backward compatibility
+ # If use-web is set, then:
+ # Move "service dns dynamic address <address> <service|rfc2136> <service> ..."
+ # to "service dns dynamic address web <service|rfc2136> <service>-<address> ..."
+ # Move "service dns dynamic address web use-web ..."
+ # to "service dns dynamic address web web-options ..."
+ # Note: The config is named <service>-<address> to avoid name conflict with old entries
if config.exists(new_base_path + [address, 'use-web']):
- config.rename(new_base_path + [address], 'web')
- config.rename(new_base_path + ['web', 'use-web'], 'web-options')
+ for svc_type in ['rfc2136', 'service']:
+ if config.exists(new_base_path + [address, svc_type]):
+ config.set(new_base_path + ['web', svc_type])
+ config.set_tag(new_base_path + ['web', svc_type])
+ for svc_cfg in config.list_nodes(new_base_path + [address, svc_type]):
+ config.copy(new_base_path + [address, svc_type, svc_cfg],
+ new_base_path + ['web', svc_type, f'{svc_cfg}-{address}'])
+
+ # Multiple web-options were not supported, so copy only the first one
+ if not config.exists(new_base_path + ['web', 'web-options']):
+ config.copy(new_base_path + [address, 'use-web'], new_base_path + ['web', 'web-options'])
+
+ config.delete(new_base_path + [address])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dns-dynamic/2-to-3 b/src/migration-scripts/dns-dynamic/2-to-3
index 187c2a895..e5910f7b4 100755
--- a/src/migration-scripts/dns-dynamic/2-to-3
+++ b/src/migration-scripts/dns-dynamic/2-to-3
@@ -21,10 +21,27 @@
# 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> ..."
+# - normalize the all service names to conform with name constraints
import sys
+import re
+from unicodedata import normalize
from vyos.configtree import ConfigTree
+def normalize_name(name):
+ """Normalize service names to conform with name constraints.
+
+ This is necessary as part of migration because there were no constraints in
+ the old name format.
+ """
+ # Normalize unicode characters to ASCII (NFKD)
+ # Replace all separators with hypens, strip leading and trailing hyphens
+ name = normalize('NFKD', name).encode('ascii', 'ignore').decode()
+ name = re.sub(r'(\s|\W)+', '-', name).strip('-')
+
+ return name
+
+
if len(sys.argv) < 2:
print("Must specify file name!")
sys.exit(1)
@@ -64,22 +81,36 @@ for address in config.list_nodes(address_path):
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'
+ # Set protocol to 'nsupdate' for RFC2136 configuration
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')
+ for rfc_cfg in config.list_nodes(address_path_tag + ['rfc2136']):
+ config.set(address_path_tag + ['rfc2136', rfc_cfg, '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>'
+ # And then copy the services from 'address <interface> service <service>'
+ # to 'name (service|rfc2136)-<service>-<address>'
+ # Note: The new service is named (service|rfc2136)-<service>-<address>
+ # to avoid name conflict with old entries
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])
+ config.copy(address_path_tag + [svc_type, svc_cfg],
+ name_path + ['-'.join([svc_type, svc_cfg, address])])
# Finally cleanup the old address path
config.delete(address_path)
+# Normalize the all service names to conform with name constraints
+index = 1
+for name in config.list_nodes(name_path):
+ new_name = normalize_name(name)
+ if new_name != name:
+ # Append index if there is still a name conflicts after normalization
+ # For example, "foo-?(" and "foo-!)" both normalize to "foo-"
+ if config.exists(name_path + [new_name]):
+ new_name = f'{new_name}-{index}'
+ index += 1
+ config.rename(name_path + [name], new_name)
+
try:
with open(file_name, 'w') as f:
f.write(config.to_string())
diff --git a/src/migration-scripts/nat/6-to-7 b/src/migration-scripts/nat/6-to-7
index b5f6328ef..a2e735394 100755
--- a/src/migration-scripts/nat/6-to-7
+++ b/src/migration-scripts/nat/6-to-7
@@ -21,6 +21,7 @@
# to
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] name <iface>'
# 'set nat [source|destination] rule X [inbound-interface|outbound interface] group <iface_group>'
+# Also remove command if interface == any
from sys import argv,exit
from vyos.configtree import ConfigTree
@@ -56,8 +57,11 @@ for direction in ['source', 'destination']:
if config.exists(base + [iface]):
if config.exists(base + [iface, 'interface-name']):
tmp = config.return_value(base + [iface, 'interface-name'])
- config.delete(base + [iface, 'interface-name'])
- config.set(base + [iface, 'name'], value=tmp)
+ if tmp != 'any':
+ config.delete(base + [iface, 'interface-name'])
+ config.set(base + [iface, 'name'], value=tmp)
+ else:
+ config.delete(base + [iface])
try:
with open(file_name, 'w') as f:
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index bd2c522ca..a9271ea79 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -102,11 +102,11 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig
if family == 'inet':
data_lease['mac'] = lease['hwaddr']
- data_lease['start'] = lease['start_timestamp']
+ data_lease['start'] = lease['start_timestamp'].timestamp()
data_lease['hostname'] = lease['hostname']
if family == 'inet6':
- data_lease['last_communication'] = lease['start_timestamp']
+ data_lease['last_communication'] = lease['start_timestamp'].timestamp()
data_lease['iaid_duid'] = _format_hex_string(lease['duid'])
lease_types_long = {'0': 'non-temporary', '1': 'temporary', '2': 'prefix delegation'}
data_lease['type'] = lease_types_long[lease['lease_type']]
@@ -123,7 +123,7 @@ def _get_raw_server_leases(family='inet', pool=None, sorted=None, state=[], orig
# Do not add old leases
if data_lease['remaining'] != '' and data_lease['pool'] in pool and data_lease['state'] != 'free':
- if not state or data_lease['state'] in state:
+ if not state or state == 'all' or data_lease['state'] in state:
data.append(data_lease)
# deduplicate
@@ -151,7 +151,7 @@ def _get_formatted_server_leases(raw_data, family='inet'):
ipaddr = lease.get('ip')
hw_addr = lease.get('mac')
state = lease.get('state')
- start = lease.get('start').timestamp()
+ start = lease.get('start')
start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
end = lease.get('end')
end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S') if end else '-'
@@ -168,7 +168,7 @@ def _get_formatted_server_leases(raw_data, family='inet'):
for lease in raw_data:
ipaddr = lease.get('ip')
state = lease.get('state')
- start = lease.get('last_communication').timestamp()
+ start = lease.get('last_communication')
start = _utc_to_local(start).strftime('%Y/%m/%d %H:%M:%S')
end = lease.get('end')
end = _utc_to_local(end).strftime('%Y/%m/%d %H:%M:%S')
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index b3e6e518c..6a8797aec 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -22,6 +22,7 @@ from pathlib import Path
from shutil import copy, chown, rmtree, copytree
from glob import glob
from sys import exit
+from os import environ
from time import sleep
from typing import Union
from urllib.parse import urlparse
@@ -83,6 +84,8 @@ DIR_KERNEL_SRC: str = '/boot/'
FILE_ROOTFS_SRC: str = '/usr/lib/live/mount/medium/live/filesystem.squashfs'
ISO_DOWNLOAD_PATH: str = '/tmp/vyos_installation.iso'
+external_download_script = '/usr/libexec/vyos/simple-download.py'
+
# default boot variables
DEFAULT_BOOT_VARS: dict[str, str] = {
'timeout': '5',
@@ -179,6 +182,7 @@ def create_partitions(target_disk: str, target_size: int,
rootfs_size: int = available_size
print(MSG_INFO_INSTALL_PARTITONING)
+ raid.clear()
disk.disk_cleanup(target_disk)
disk_details: disk.DiskDetails = disk.parttable_create(target_disk,
rootfs_size)
@@ -459,8 +463,23 @@ def validate_signature(file_path: str, sign_type: str) -> None:
else:
print('Signature is valid')
-
-def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
+def download_file(local_file: str, remote_path: str, vrf: str,
+ username: str, password: str,
+ progressbar: bool = False, check_space: bool = False):
+ environ['REMOTE_USERNAME'] = username
+ environ['REMOTE_PASSWORD'] = password
+ if vrf is None:
+ download(local_file, remote_path, progressbar=progressbar,
+ check_space=check_space, raise_error=True)
+ else:
+ vrf_cmd = f'REMOTE_USERNAME={username} REMOTE_PASSWORD={password} \
+ ip vrf exec {vrf} {external_download_script} \
+ --local-file {local_file} --remote-path {remote_path}'
+ cmd(vrf_cmd)
+
+def image_fetch(image_path: str, vrf: str = None,
+ username: str = '', password: str = '',
+ no_prompt: bool = False) -> Path:
"""Fetch an ISO image
Args:
@@ -473,14 +492,17 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
# check a type of path
if urlparse(image_path).scheme:
# download an image
- download(ISO_DOWNLOAD_PATH, image_path, True, True,
- raise_error=True)
+ download_file(ISO_DOWNLOAD_PATH, image_path, vrf,
+ username, password,
+ progressbar=True, check_space=True)
+
# download a signature
sign_file = (False, '')
for sign_type in ['minisig', 'asc']:
try:
- download(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
- f'{image_path}.{sign_type}', raise_error=True)
+ download_file(f'{ISO_DOWNLOAD_PATH}.{sign_type}',
+ f'{image_path}.{sign_type}', vrf,
+ username, password)
sign_file = (True, sign_type)
break
except Exception:
@@ -501,8 +523,8 @@ def image_fetch(image_path: str, no_prompt: bool = False) -> Path:
return local_path
else:
raise FileNotFoundError
- except Exception:
- print(f'The image cannot be fetched from: {image_path}')
+ except Exception as e:
+ print(f'The image cannot be fetched from: {image_path} {e}')
exit(1)
@@ -611,7 +633,8 @@ def install_image() -> None:
print(MSG_WARN_IMAGE_NAME_WRONG)
# ask for password
- user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos')
+ user_password: str = ask_input(MSG_INPUT_PASSWORD, default='vyos',
+ no_echo=True)
# ask for default console
console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE,
@@ -730,7 +753,8 @@ def install_image() -> None:
@compat.grub_cfg_update
-def add_image(image_path: str, no_prompt: bool = False) -> None:
+def add_image(image_path: str, vrf: str = None, username: str = '',
+ password: str = '', no_prompt: bool = False) -> None:
"""Add a new image
Args:
@@ -740,7 +764,7 @@ def add_image(image_path: str, no_prompt: bool = False) -> None:
exit(MSG_ERR_LIVE)
# fetch an image
- iso_path: Path = image_fetch(image_path, no_prompt)
+ iso_path: Path = image_fetch(image_path, vrf, username, password, no_prompt)
try:
# mount an ISO
Path(DIR_ISO_MOUNT).mkdir(mode=0o755, parents=True)
@@ -840,10 +864,15 @@ def parse_arguments() -> Namespace:
choices=['install', 'add'],
required=True,
help='action to perform with an image')
+ parser.add_argument('--vrf',
+ help='vrf name for image download')
parser.add_argument('--no-prompt', action='store_true',
help='perform action non-interactively')
- parser.add_argument(
- '--image-path',
+ parser.add_argument('--username', default='',
+ help='username for image download')
+ parser.add_argument('--password', default='',
+ help='password for image download')
+ parser.add_argument('--image-path',
help='a path (HTTP or local file) to an image that needs to be installed'
)
# parser.add_argument('--image_new_name', help='a new name for image')
@@ -861,7 +890,8 @@ if __name__ == '__main__':
if args.action == 'install':
install_image()
if args.action == 'add':
- add_image(args.image_path, args.no_prompt)
+ add_image(args.image_path, args.vrf,
+ args.username, args.password, args.no_prompt)
exit()
diff --git a/src/system/on-dhcp-event.sh b/src/system/on-dhcp-event.sh
index 7b25bf338..03574bdc3 100755
--- a/src/system/on-dhcp-event.sh
+++ b/src/system/on-dhcp-event.sh
@@ -8,7 +8,7 @@
# Thanks to forum user "ruudboon" for multiple domain fix
# Thanks to forum user "chibby85" for expire patch and static-mapping
-if [ $# -lt 5 ]; then
+if [ $# -lt 1 ]; then
echo Invalid args
logger -s -t on-dhcp-event "Invalid args \"$@\""
exit 1
@@ -18,36 +18,30 @@ action=$1
client_name=$LEASE4_HOSTNAME
client_ip=$LEASE4_ADDRESS
client_mac=$LEASE4_HWADDR
-domain=$(echo "$client_name" | cut -d"." -f2-)
hostsd_client="/usr/bin/vyos-hostsd-client"
case "$action" in
- leases4_renew|lease4_recover) # add mapping for new lease
+ lease4_renew|lease4_recover) # add mapping for new/recovered lease address
if [ -z "$client_name" ]; then
logger -s -t on-dhcp-event "Client name was empty, using MAC \"$client_mac\" instead"
- client_name=$(echo "client-"$client_mac | tr : -)
+ client_name=$(echo "host-$client_mac" | tr : -)
fi
- if [ -z "$domain" ]; then
- client_fqdn_name=$client_name
- client_search_expr=$client_name
- else
- client_fqdn_name=$client_name.$domain
- client_search_expr="$client_name\\.$domain"
- fi
- $hostsd_client --add-hosts "$client_fqdn_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
+ $hostsd_client --add-hosts "$client_name,$client_ip" --tag "dhcp-server-$client_ip" --apply
exit 0
;;
- lease4_release|lease4_expire) # delete mapping for released address)
+ lease4_release|lease4_expire|lease4_decline) # delete mapping for released/declined address
$hostsd_client --delete-hosts --tag "dhcp-server-$client_ip" --apply
exit 0
;;
+ leases4_committed) # nothing to do
+ exit 0
+ ;;
+
*)
logger -s -t on-dhcp-event "Invalid command \"$1\""
exit 1
;;
esac
-
-exit 0
diff --git a/src/validators/bgp-large-community-list b/src/validators/bgp-large-community-list
index 80112dfdc..9ba5b27eb 100755
--- a/src/validators/bgp-large-community-list
+++ b/src/validators/bgp-large-community-list
@@ -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
@@ -17,9 +17,8 @@
import re
import sys
-from vyos.template import is_ipv4
-
pattern = '(.*):(.*):(.*)'
+allowedChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '+', '*', '?', '^', '$', '(', ')', '[', ']', '{', '}', '|', '\\', ':', '-' }
if __name__ == '__main__':
if len(sys.argv) != 2:
@@ -29,8 +28,7 @@ if __name__ == '__main__':
if not len(value) == 3:
sys.exit(1)
- if not (re.match(pattern, sys.argv[1]) and
- (is_ipv4(value[0]) or value[0].isdigit()) and (value[1].isdigit() or value[1] == '*')):
+ if not (re.match(pattern, sys.argv[1]) and set(sys.argv[1]).issubset(allowedChars)):
sys.exit(1)
sys.exit(0)