summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md4
-rw-r--r--data/templates/conntrack/nftables-helpers.j26
-rw-r--r--data/templates/dhcp-client/override.conf.j23
-rw-r--r--data/templates/dhcp-server/kea-ctrl-agent.conf.j24
-rw-r--r--data/templates/dhcp-server/kea-dhcp4.conf.j24
-rw-r--r--data/templates/router-advert/radvd.conf.j27
-rw-r--r--debian/control3
-rw-r--r--interface-definitions/container.xml.in21
-rw-r--r--interface-definitions/include/constraint/container-network.xml.i6
-rw-r--r--interface-definitions/include/constraint/dhcp-client-string-option.xml.i2
-rw-r--r--interface-definitions/include/firewall/conntrack-helper.xml.i6
-rw-r--r--interface-definitions/include/generic-description.xml.i2
-rw-r--r--interface-definitions/include/interface/vif-s.xml.i22
-rw-r--r--interface-definitions/include/interface/vlan-protocol.xml.i23
-rw-r--r--interface-definitions/include/qos/mtu.xml.i14
-rw-r--r--interface-definitions/include/version/dhcp-server-version.xml.i2
-rw-r--r--interface-definitions/interfaces_bridge.xml.in4
-rw-r--r--interface-definitions/nat64.xml.in2
-rw-r--r--interface-definitions/nat66.xml.in2
-rw-r--r--interface-definitions/pki.xml.in2
-rw-r--r--interface-definitions/policy.xml.in12
-rw-r--r--interface-definitions/qos.xml.in2
-rw-r--r--interface-definitions/service_config-sync.xml.in460
-rw-r--r--interface-definitions/service_dhcp-server.xml.in12
-rw-r--r--interface-definitions/service_router-advert.xml.in30
-rw-r--r--interface-definitions/system_conntrack.xml.in6
-rw-r--r--op-mode-definitions/container.xml.in35
-rw-r--r--op-mode-definitions/dhcp.xml.in4
-rw-r--r--op-mode-definitions/force-commit-archive.xml.in13
-rw-r--r--op-mode-definitions/pki.xml.in2
-rw-r--r--op-mode-definitions/show-log.xml.in4
-rw-r--r--python/vyos/config_mgmt.py3
-rw-r--r--python/vyos/configtree.py24
-rw-r--r--python/vyos/configverify.py27
-rw-r--r--python/vyos/ethtool.py24
-rw-r--r--python/vyos/frr.py16
-rw-r--r--python/vyos/ifconfig/bridge.py34
-rw-r--r--python/vyos/ifconfig/ethernet.py31
-rw-r--r--python/vyos/ifconfig/vti.py8
-rw-r--r--python/vyos/opmode.py2
-rw-r--r--python/vyos/priority.py75
-rw-r--r--python/vyos/qos/base.py9
-rw-r--r--python/vyos/system/compat.py22
-rw-r--r--python/vyos/system/grub.py38
-rw-r--r--python/vyos/template.py4
-rwxr-xr-xpython/vyos/xml_ref/generate_cache.py3
-rw-r--r--smoketest/config-tests/dialup-router-medium-vpn8
-rwxr-xr-xsmoketest/scripts/cli/test_backslash_escape.py68
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py31
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py18
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vti.py19
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py5
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py8
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py20
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py41
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py10
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py28
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py8
-rwxr-xr-xsrc/conf_mode/firewall.py12
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py2
-rwxr-xr-xsrc/conf_mode/protocols_isis.py16
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py7
-rwxr-xr-xsrc/conf_mode/service_dhcp-server.py48
-rwxr-xr-xsrc/conf_mode/service_router-advert.py18
-rwxr-xr-xsrc/conf_mode/system_conntrack.py43
-rwxr-xr-xsrc/etc/ipsec.d/vti-up-down4
-rwxr-xr-xsrc/helpers/priority.py42
-rwxr-xr-xsrc/helpers/vyos_config_sync.py72
-rwxr-xr-xsrc/migration-scripts/dhcp-server/10-to-1148
-rwxr-xr-xsrc/migration-scripts/dhcpv6-server/4-to-57
-rwxr-xr-xsrc/migration-scripts/policy/1-to-218
-rwxr-xr-xsrc/migration-scripts/policy/3-to-48
-rwxr-xr-xsrc/op_mode/conntrack.py3
-rwxr-xr-xsrc/op_mode/dhcp.py32
-rwxr-xr-xsrc/op_mode/image_installer.py4
-rwxr-xr-xsrc/services/vyos-http-api-server2
-rw-r--r--src/system/grub_update.py4
-rw-r--r--src/systemd/dhclient@.service1
-rw-r--r--src/systemd/vyos-grub-update.service4
80 files changed, 1405 insertions, 294 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 933894447..cd348ead7 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -18,8 +18,8 @@ the box, please use [x]
- [ ] Other (please describe):
## Related Task(s)
-<!-- All submitted PRs must be linked to a Task on Phabricator. -->
-* https://vyos.dev/Txxxx
+<!-- optional: Link to related other tasks on Phabricator. -->
+<!-- * https://vyos.dev/Txxxx -->
## Related PR(s)
<!-- Link here any PRs in other repositories that are required by this PR -->
diff --git a/data/templates/conntrack/nftables-helpers.j2 b/data/templates/conntrack/nftables-helpers.j2
index 433931162..63a0cc855 100644
--- a/data/templates/conntrack/nftables-helpers.j2
+++ b/data/templates/conntrack/nftables-helpers.j2
@@ -31,6 +31,12 @@
}
{% endif %}
+{% if modules.rtsp is vyos_defined and ipv4 %}
+ ct helper rtsp_tcp {
+ type "rtsp" protocol tcp;
+ }
+{% endif %}
+
{% if modules.sip is vyos_defined %}
ct helper sip_tcp {
type "sip" protocol tcp;
diff --git a/data/templates/dhcp-client/override.conf.j2 b/data/templates/dhcp-client/override.conf.j2
index d09320270..c2e059c7b 100644
--- a/data/templates/dhcp-client/override.conf.j2
+++ b/data/templates/dhcp-client/override.conf.j2
@@ -3,9 +3,6 @@
{% set if_metric = '-e IF_METRIC=' ~ dhcp_options.default_route_distance if dhcp_options.default_route_distance is vyos_defined else '' %}
{% set dhclient_options = '-d -nw -cf ' ~ isc_dhclient_dir ~ '/dhclient_' ~ ifname ~ '.conf -pf ' ~ isc_dhclient_dir ~ '/dhclient_' ~ ifname ~ '.pid -lf ' ~ isc_dhclient_dir ~ '/dhclient_' ~ ifname ~ '.leases ' ~ if_metric %}
-[Unit]
-ConditionPathExists={{ isc_dhclient_dir }}/dhclient_%i.conf
-
[Service]
ExecStart=
ExecStart={{ vrf_command }}/sbin/dhclient -4 {{ dhclient_options }} {{ ifname }}
diff --git a/data/templates/dhcp-server/kea-ctrl-agent.conf.j2 b/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
index 74c63a7a0..b37cf4798 100644
--- a/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
+++ b/data/templates/dhcp-server/kea-ctrl-agent.conf.j2
@@ -1,7 +1,7 @@
{
"Control-agent": {
-{% if failover is vyos_defined %}
- "http-host": "{{ failover.source_address }}",
+{% if high_availability is vyos_defined %}
+ "http-host": "{{ high_availability.source_address }}",
"http-port": 647,
"control-sockets": {
"dhcp4": {
diff --git a/data/templates/dhcp-server/kea-dhcp4.conf.j2 b/data/templates/dhcp-server/kea-dhcp4.conf.j2
index 629fa952a..bf37b94f6 100644
--- a/data/templates/dhcp-server/kea-dhcp4.conf.j2
+++ b/data/templates/dhcp-server/kea-dhcp4.conf.j2
@@ -51,11 +51,11 @@
}
],
"hooks-libraries": [
-{% if failover is vyos_defined %}
+{% if high_availability is vyos_defined %}
{
"library": "/usr/lib/{{ machine }}-linux-gnu/kea/hooks/libdhcp_ha.so",
"parameters": {
- "high-availability": [{{ failover | kea_failover_json }}]
+ "high-availability": [{{ high_availability | kea_high_availability_json }}]
}
},
{% endif %}
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index 4ef4751dd..97180d164 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -50,6 +50,13 @@ interface {{ iface }} {
{% endfor %}
};
{% endif %}
+{% if iface_config.nat64prefix is vyos_defined %}
+{% for nat64prefix, nat64prefix_options in iface_config.nat64prefix.items() %}
+ nat64prefix {{ nat64prefix }} {
+ AdvValidLifetime {{ nat64prefix_options.valid_lifetime }};
+ };
+{% endfor %}
+{% endif %}
{% if iface_config.prefix is vyos_defined %}
{% for prefix, prefix_options in iface_config.prefix.items() %}
prefix {{ prefix }} {
diff --git a/debian/control b/debian/control
index dddc4e14c..c5a60f660 100644
--- a/debian/control
+++ b/debian/control
@@ -256,6 +256,9 @@ Depends:
# For "nat64"
jool,
# End "nat64"
+# For "system conntrack modules rtsp"
+ nat-rtsp,
+# End "system conntrack modules rtsp"
# For "system ntp"
chrony,
# End "system ntp"
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index f0db8a6f2..7e1f4811a 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -128,7 +128,17 @@
</leafNode>
<leafNode name="image">
<properties>
- <help>Image name in the hub-registry</help>
+ <help>Container image to use</help>
+ <completionHelp>
+ <script>sudo podman image list --format "{{.Repository}}:{{.Tag}}"</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Image name in the hub-registry</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,255}</regex>
+ </constraint>
</properties>
</leafNode>
<leafNode name="command">
@@ -165,6 +175,9 @@
<format>txt</format>
<description>Set label option value</description>
</valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,255}</regex>
+ </constraint>
</properties>
</leafNode>
</children>
@@ -211,6 +224,7 @@
<completionHelp>
<path>container network</path>
</completionHelp>
+ #include <include/constraint/container-network.xml.i>
</properties>
<children>
<leafNode name="address">
@@ -426,10 +440,7 @@
<tagNode name="network">
<properties>
<help>Network name</help>
- <constraint>
- <regex>[-_a-zA-Z0-9]{1,11}</regex>
- </constraint>
- <constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
+ #include <include/constraint/container-network.xml.i>
</properties>
<children>
#include <include/generic-description.xml.i>
diff --git a/interface-definitions/include/constraint/container-network.xml.i b/interface-definitions/include/constraint/container-network.xml.i
new file mode 100644
index 000000000..6f0f06d6f
--- /dev/null
+++ b/interface-definitions/include/constraint/container-network.xml.i
@@ -0,0 +1,6 @@
+<!-- include start from constraint/container-network.xml.i -->
+<constraint>
+ <regex>[-_a-zA-Z0-9]{1,11}</regex>
+</constraint>
+<constraintErrorMessage>Network name cannot be longer than 11 characters</constraintErrorMessage>
+<!-- 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 88257a9bb..0e3fb8a96 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 constraint/dhcp-client-string-option.xml.i -->
-<regex>[-_a-zA-Z0-9\s]+</regex>
+<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/conntrack-helper.xml.i b/interface-definitions/include/firewall/conntrack-helper.xml.i
index ee17f2c61..3ca1a0353 100644
--- a/interface-definitions/include/firewall/conntrack-helper.xml.i
+++ b/interface-definitions/include/firewall/conntrack-helper.xml.i
@@ -22,6 +22,10 @@
<description>Related traffic from NFS helper</description>
</valueHelp>
<valueHelp>
+ <format>rtsp</format>
+ <description>Related traffic from RTSP helper</description>
+ </valueHelp>
+ <valueHelp>
<format>sip</format>
<description>Related traffic from SIP helper</description>
</valueHelp>
@@ -34,7 +38,7 @@
<description>Related traffic from SQLNet helper</description>
</valueHelp>
<constraint>
- <regex>(ftp|h323|pptp|nfs|sip|tftp|sqlnet)</regex>
+ <regex>(ftp|h323|pptp|nfs|rtsp|sip|tftp|sqlnet)</regex>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/generic-description.xml.i b/interface-definitions/include/generic-description.xml.i
index 0b3701534..7e091eae7 100644
--- a/interface-definitions/include/generic-description.xml.i
+++ b/interface-definitions/include/generic-description.xml.i
@@ -7,7 +7,7 @@
<description>Description</description>
</valueHelp>
<constraint>
- <regex>[[:ascii:]]{0,255}</regex>
+ <regex>.{0,255}</regex>
</constraint>
<constraintErrorMessage>Description too long (limit 255 characters)</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/include/interface/vif-s.xml.i b/interface-definitions/include/interface/vif-s.xml.i
index fdd62b63d..02e7ab057 100644
--- a/interface-definitions/include/interface/vif-s.xml.i
+++ b/interface-definitions/include/interface/vif-s.xml.i
@@ -18,27 +18,7 @@
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/disable-link-detect.xml.i>
#include <include/interface/disable.xml.i>
- <leafNode name="protocol">
- <properties>
- <help>Protocol used for service VLAN (default: 802.1ad)</help>
- <completionHelp>
- <list>802.1ad 802.1q</list>
- </completionHelp>
- <valueHelp>
- <format>802.1ad</format>
- <description>Provider Bridging (IEEE 802.1ad, Q-inQ), ethertype 0x88a8</description>
- </valueHelp>
- <valueHelp>
- <format>802.1q</format>
- <description>VLAN-tagged frame (IEEE 802.1q), ethertype 0x8100</description>
- </valueHelp>
- <constraint>
- <regex>(802.1q|802.1ad)</regex>
- </constraint>
- <constraintErrorMessage>Ethertype must be 802.1ad or 802.1q</constraintErrorMessage>
- </properties>
- <defaultValue>802.1ad</defaultValue>
- </leafNode>
+ #include <include/interface/vlan-protocol.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
#include <include/interface/mac.xml.i>
diff --git a/interface-definitions/include/interface/vlan-protocol.xml.i b/interface-definitions/include/interface/vlan-protocol.xml.i
new file mode 100644
index 000000000..2fe8d65d7
--- /dev/null
+++ b/interface-definitions/include/interface/vlan-protocol.xml.i
@@ -0,0 +1,23 @@
+<!-- include start from interface/vif.xml.i -->
+<leafNode name="protocol">
+ <properties>
+ <help>Protocol used for service VLAN (default: 802.1ad)</help>
+ <completionHelp>
+ <list>802.1ad 802.1q</list>
+ </completionHelp>
+ <valueHelp>
+ <format>802.1ad</format>
+ <description>Provider Bridging (IEEE 802.1ad, Q-inQ), ethertype 0x88a8</description>
+ </valueHelp>
+ <valueHelp>
+ <format>802.1q</format>
+ <description>VLAN-tagged frame (IEEE 802.1q), ethertype 0x8100</description>
+ </valueHelp>
+ <constraint>
+ <regex>(802.1q|802.1ad)</regex>
+ </constraint>
+ <constraintErrorMessage>Ethertype must be 802.1ad or 802.1q</constraintErrorMessage>
+ </properties>
+ <defaultValue>802.1ad</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/qos/mtu.xml.i b/interface-definitions/include/qos/mtu.xml.i
new file mode 100644
index 000000000..161d4c27f
--- /dev/null
+++ b/interface-definitions/include/qos/mtu.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from qos/mtu.xml.i -->
+<leafNode name="mtu">
+ <properties>
+ <help>MTU size for this class</help>
+ <valueHelp>
+ <format>u32:256-65535</format>
+ <description>Bytes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 256-65535"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/dhcp-server-version.xml.i b/interface-definitions/include/version/dhcp-server-version.xml.i
index 3dcbc513a..71f3d4a36 100644
--- a/interface-definitions/include/version/dhcp-server-version.xml.i
+++ b/interface-definitions/include/version/dhcp-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/dhcp-server-version.xml.i -->
-<syntaxVersion component='dhcp-server' version='10'></syntaxVersion>
+<syntaxVersion component='dhcp-server' version='11'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_bridge.xml.in b/interface-definitions/interfaces_bridge.xml.in
index d4d277cfc..7fb5f121a 100644
--- a/interface-definitions/interfaces_bridge.xml.in
+++ b/interface-definitions/interfaces_bridge.xml.in
@@ -98,6 +98,10 @@
<valueless/>
</properties>
</leafNode>
+ #include <include/interface/vlan-protocol.xml.i>
+ <leafNode name="protocol">
+ <defaultValue>802.1q</defaultValue>
+ </leafNode>
<leafNode name="max-age">
<properties>
<help>Interval at which neighbor bridges are removed</help>
diff --git a/interface-definitions/nat64.xml.in b/interface-definitions/nat64.xml.in
index dfdd295d2..4b3c157cc 100644
--- a/interface-definitions/nat64.xml.in
+++ b/interface-definitions/nat64.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="nat64" owner="${vyos_conf_scripts_dir}/nat64.py">
<properties>
- <help>IPv6-to-IPv4 Network Address Translation (NAT64) Settings</help>
+ <help>Network Address Translation (NAT64) parameters</help>
<priority>501</priority>
</properties>
<children>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index 1518de8bd..32d501cce 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="nat66" owner="${vyos_conf_scripts_dir}/nat66.py">
<properties>
- <help>IPv6-to-IPv6 Network Prefix Translation (NAT66/NPT) Settings</help>
+ <help>Network Prefix Translation (NAT66/NPTv6) parameters</help>
<priority>500</priority>
</properties>
<children>
diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in
index 7a0b073b4..b922771c1 100644
--- a/interface-definitions/pki.xml.in
+++ b/interface-definitions/pki.xml.in
@@ -2,7 +2,7 @@
<interfaceDefinition>
<node name="pki" owner="${vyos_conf_scripts_dir}/pki.py">
<properties>
- <help>VyOS PKI configuration</help>
+ <help>Public key infrastructure (PKI)</help>
<priority>300</priority>
</properties>
<children>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 0d82cd3f8..791fa1d87 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1124,12 +1124,20 @@
<leafNode name="exclude">
<properties>
<help>Remove/exclude from the as-path attribute</help>
+ <completionHelp>
+ <list>all</list>
+ </completionHelp>
<valueHelp>
- <format>u32</format>
+ <format>u32:1-4294967295</format>
<description>AS number</description>
</valueHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Exclude all AS numbers from the as-path</description>
+ </valueHelp>
<constraint>
<validator name="as-number-list"/>
+ <regex>(all)</regex>
</constraint>
</properties>
</leafNode>
@@ -1137,7 +1145,7 @@
<properties>
<help>Prepend to the as-path</help>
<valueHelp>
- <format>u32</format>
+ <format>u32:1-4294967295</format>
<description>AS number</description>
</valueHelp>
<constraint>
diff --git a/interface-definitions/qos.xml.in b/interface-definitions/qos.xml.in
index 31b9a7d21..7618c3027 100644
--- a/interface-definitions/qos.xml.in
+++ b/interface-definitions/qos.xml.in
@@ -278,6 +278,7 @@
#include <include/generic-description.xml.i>
#include <include/qos/bandwidth.xml.i>
#include <include/qos/burst.xml.i>
+ #include <include/qos/mtu.xml.i>
#include <include/qos/class-police-exceed.xml.i>
#include <include/qos/class-match.xml.i>
#include <include/qos/class-priority.xml.i>
@@ -293,6 +294,7 @@
<children>
#include <include/qos/bandwidth.xml.i>
#include <include/qos/burst.xml.i>
+ #include <include/qos/mtu.xml.i>
#include <include/qos/class-police-exceed.xml.i>
</children>
</node>
diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in
index 9955acfee..cb51a33b1 100644
--- a/interface-definitions/service_config-sync.xml.in
+++ b/interface-definitions/service_config-sync.xml.in
@@ -38,11 +38,11 @@
<properties>
<help>Connection API timeout</help>
<valueHelp>
- <format>u32:1-300</format>
+ <format>u32:1-3600</format>
<description>Connection API timeout</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-300"/>
+ <validator name="numeric" argument="--range 1-3600"/>
</constraint>
</properties>
<defaultValue>60</defaultValue>
@@ -73,30 +73,444 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="section">
+ <node name="section">
<properties>
<help>Section for synchronization</help>
- <completionHelp>
- <list>nat nat66 firewall</list>
- </completionHelp>
- <valueHelp>
- <format>nat</format>
- <description>NAT</description>
- </valueHelp>
- <valueHelp>
- <format>nat66</format>
- <description>NAT66</description>
- </valueHelp>
- <valueHelp>
- <format>firewall</format>
- <description>firewall</description>
- </valueHelp>
- <constraint>
- <regex>(nat|nat66|firewall)</regex>
- </constraint>
- <multi/>
</properties>
- </leafNode>
+ <children>
+ <leafNode name="firewall">
+ <properties>
+ <help>Firewall</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="interfaces">
+ <properties>
+ <help>Interfaces</help>
+ </properties>
+ <children>
+ <leafNode name="bonding">
+ <properties>
+ <help>Bonding interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="bridge">
+ <properties>
+ <help>Bridge interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dummy">
+ <properties>
+ <help>Dummy interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ethernet">
+ <properties>
+ <help>Ethernet interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="geneve">
+ <properties>
+ <help>GENEVE interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="input">
+ <properties>
+ <help>Input interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="l2tpv3">
+ <properties>
+ <help>L2TPv3 interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="loopback">
+ <properties>
+ <help>Loopback interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="macsec">
+ <properties>
+ <help>MACsec interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="openvpn">
+ <properties>
+ <help>OpenVPN interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="pppoe">
+ <properties>
+ <help>PPPoE interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="pseudo-ethernet">
+ <properties>
+ <help>Pseudo-Ethernet interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="sstpc">
+ <properties>
+ <help>SSTP client interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="tunnel">
+ <properties>
+ <help>Tunnel interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="virtual-ethernet">
+ <properties>
+ <help>Virtual Ethernet interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="vti">
+ <properties>
+ <help>Virtual tunnel interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="vxlan">
+ <properties>
+ <help>VXLAN interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="wireguard">
+ <properties>
+ <help>Wireguard interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="wireless">
+ <properties>
+ <help>Wireless interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="wwan">
+ <properties>
+ <help>WWAN interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="nat">
+ <properties>
+ <help>NAT</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="nat66">
+ <properties>
+ <help>NAT66</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="pki">
+ <properties>
+ <help>Public key infrastructure (PKI)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="policy">
+ <properties>
+ <help>Routing policy</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="protocols">
+ <properties>
+ <help>Routing protocols</help>
+ </properties>
+ <children>
+ <leafNode name="babel">
+ <properties>
+ <help>Babel Routing Protocol</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="bfd">
+ <properties>
+ <help>Bidirectional Forwarding Detection (BFD)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="bgp">
+ <properties>
+ <help>Border Gateway Protocol (BGP)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="failover">
+ <properties>
+ <help>Failover route</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="igmp-proxy">
+ <properties>
+ <help>Internet Group Management Protocol (IGMP) proxy</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="isis">
+ <properties>
+ <help>Intermediate System to Intermediate System (IS-IS)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="mpls">
+ <properties>
+ <help>Multiprotocol Label Switching (MPLS)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="nhrp">
+ <properties>
+ <help>Next Hop Resolution Protocol (NHRP) parameters</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospf">
+ <properties>
+ <help>Open Shortest Path First (OSPF)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ospfv3">
+ <properties>
+ <help>Open Shortest Path First (OSPF) for IPv6</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="pim">
+ <properties>
+ <help>Protocol Independent Multicast (PIM) and IGMP</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="pim6">
+ <properties>
+ <help>Protocol Independent Multicast for IPv6 (PIMv6) and MLD</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rip">
+ <properties>
+ <help>Routing Information Protocol (RIP) parameters</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ripng">
+ <properties>
+ <help>Routing Information Protocol (RIPng) parameters</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="rpki">
+ <properties>
+ <help>Resource Public Key Infrastructure (RPKI)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="segment-routing">
+ <properties>
+ <help>Segment Routing</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="static">
+ <properties>
+ <help>Static Routing</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="qos">
+ <properties>
+ <help>Quality of Service (QoS)</help>
+ </properties>
+ <children>
+ <leafNode name="interface">
+ <properties>
+ <help>Interface to apply QoS policy</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="policy">
+ <properties>
+ <help>Service Policy definitions</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="service">
+ <properties>
+ <help>System services</help>
+ </properties>
+ <children>
+ <leafNode name="console-server">
+ <properties>
+ <help>Serial Console Server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcp-relay">
+ <properties>
+ <help>Host Configuration Protocol (DHCP) relay agent</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcp-server">
+ <properties>
+ <help>Dynamic Host Configuration Protocol (DHCP) for DHCP server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcpv6-relay">
+ <properties>
+ <help>DHCPv6 Relay Agent parameters</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dhcpv6-server">
+ <properties>
+ <help>DHCP for IPv6 (DHCPv6) server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="dns">
+ <properties>
+ <help>Domain Name System (DNS) related services</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="lldp">
+ <properties>
+ <help>LLDP settings</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="mdns">
+ <properties>
+ <help>Multicast DNS (mDNS) parameters</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="monitoring">
+ <properties>
+ <help>Monitoring services</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ndp-proxy">
+ <properties>
+ <help>Neighbor Discovery Protocol (NDP) Proxy</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ntp">
+ <properties>
+ <help>Network Time Protocol (NTP) configuration</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="snmp">
+ <properties>
+ <help>Simple Network Management Protocol (SNMP)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="tftp-server">
+ <properties>
+ <help>Trivial File Transfer Protocol (TFTP) server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="webproxy">
+ <properties>
+ <help>Webproxy service settings</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="system">
+ <properties>
+ <help>System parameters</help>
+ </properties>
+ <children>
+ <leafNode name="conntrack">
+ <properties>
+ <help>Connection Tracking</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="flow-accounting">
+ <properties>
+ <help>Flow accounting</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="option">
+ <properties>
+ <help>System Options</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="sflow">
+ <properties>
+ <help>sFlow</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="static-host-mapping">
+ <properties>
+ <help>Map host names to addresses</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="sysctl">
+ <properties>
+ <help>Configure kernel parameters at runtime</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="vpn">
+ <properties>
+ <help>Virtual Private Network (VPN)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="vrf">
+ <properties>
+ <help>Virtual Routing and Forwarding</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index 1c10a462d..2afa05a8a 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -16,18 +16,18 @@
<valueless/>
</properties>
</leafNode>
- <node name="failover">
+ <node name="high-availability">
<properties>
- <help>DHCP failover configuration</help>
+ <help>DHCP high availability configuration</help>
</properties>
<children>
#include <include/source-address-ipv4.xml.i>
<leafNode name="remote">
<properties>
- <help>IPv4 remote address used for connectio</help>
+ <help>IPv4 remote address used for connection</help>
<valueHelp>
<format>ipv4</format>
- <description>IPv4 address of failover peer</description>
+ <description>IPv4 address of high availability peer</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
@@ -45,7 +45,7 @@
</leafNode>
<leafNode name="status">
<properties>
- <help>Failover hierarchy</help>
+ <help>High availability hierarchy</help>
<completionHelp>
<list>primary secondary</list>
</completionHelp>
@@ -60,7 +60,7 @@
<constraint>
<regex>(primary|secondary)</regex>
</constraint>
- <constraintErrorMessage>Invalid DHCP failover peer status</constraintErrorMessage>
+ <constraintErrorMessage>Invalid DHCP high availability peer status</constraintErrorMessage>
</properties>
</leafNode>
#include <include/pki/ca-certificate.xml.i>
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 16c29022d..166a4a0cf 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -225,6 +225,36 @@
</leafNode>
</children>
</tagNode>
+ <tagNode name="nat64prefix">
+ <properties>
+ <help>NAT64 prefix included in the router advertisements</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix to be advertized</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="valid-lifetime">
+ <properties>
+ <help>Time in seconds that the prefix will remain valid</help>
+ <completionHelp>
+ <list>infinity</list>
+ </completionHelp>
+ <valueHelp>
+ <format>u32:4-65528</format>
+ <description>Time in seconds that the prefix will remain valid</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 4-65528"/>
+ </constraint>
+ </properties>
+ <defaultValue>65528</defaultValue>
+ </leafNode>
+ </children>
+ </tagNode>
<tagNode name="prefix">
<properties>
<help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
diff --git a/interface-definitions/system_conntrack.xml.in b/interface-definitions/system_conntrack.xml.in
index a348097cc..219c6e28e 100644
--- a/interface-definitions/system_conntrack.xml.in
+++ b/interface-definitions/system_conntrack.xml.in
@@ -289,6 +289,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="rtsp">
+ <properties>
+ <help>RTSP connection tracking</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="sip">
<properties>
<help>SIP connection tracking</help>
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index 4aa13e913..bb6f97b02 100644
--- a/op-mode-definitions/container.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -103,12 +103,28 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/container.py show_container</command>
<children>
- <leafNode name="image">
+ <node name="json">
+ <properties>
+ <help>Show containers in JSON format</help>
+ </properties>
+ <!-- no admin check -->
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_container --raw</command>
+ </node>
+ <node name="image">
<properties>
<help>Show container image</help>
</properties>
<command>sudo ${vyos_op_scripts_dir}/container.py show_image</command>
- </leafNode>
+ <children>
+ <node name="json">
+ <properties>
+ <help>Show container image in JSON format</help>
+ </properties>
+ <!-- no admin check -->
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_image --raw</command>
+ </node>
+ </children>
+ </node>
<tagNode name="log">
<properties>
<help>Show logs from a given container</help>
@@ -116,14 +132,25 @@
<path>container name</path>
</completionHelp>
</properties>
+ <!-- no admin check -->
<command>sudo podman logs --names "$4"</command>
</tagNode>
- <leafNode name="network">
+ <node name="network">
<properties>
<help>Show available container networks</help>
</properties>
+ <!-- no admin check -->
<command>sudo ${vyos_op_scripts_dir}/container.py show_network</command>
- </leafNode>
+ <children>
+ <node name="json">
+ <properties>
+ <help>Show available container networks in JSON format</help>
+ </properties>
+ <!-- no admin check -->
+ <command>sudo ${vyos_op_scripts_dir}/container.py show_network --raw</command>
+ </node>
+ </children>
+ </node>
</children>
</node>
<node name="log">
diff --git a/op-mode-definitions/dhcp.xml.in b/op-mode-definitions/dhcp.xml.in
index 3c42c8e8f..0b4a05ffe 100644
--- a/op-mode-definitions/dhcp.xml.in
+++ b/op-mode-definitions/dhcp.xml.in
@@ -293,7 +293,7 @@
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
- <command>sudo systemctl restart "dhclient@$4.service"</command>
+ <command>sudo ${vyos_op_scripts_dir}/dhcp.py renew_client_lease --family inet --interface "$4"</command>
</tagNode>
</children>
</node>
@@ -309,7 +309,7 @@
<script>${vyos_completion_dir}/list_interfaces</script>
</completionHelp>
</properties>
- <command>sudo systemctl restart "dhcp6c@$4.service"</command>
+ <command>sudo ${vyos_op_scripts_dir}/dhcp.py renew_client_lease --family inet6 --interface "$4"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/force-commit-archive.xml.in b/op-mode-definitions/force-commit-archive.xml.in
new file mode 100644
index 000000000..162323c20
--- /dev/null
+++ b/op-mode-definitions/force-commit-archive.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="force">
+ <children>
+ <leafNode name="commit-archive">
+ <properties>
+ <help>Manually archive configuration</help>
+ </properties>
+ <command>/usr/bin/config-mgmt</command>
+ </leafNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index a81c8d4f7..a5e01bade 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -4,7 +4,7 @@
<children>
<node name="pki">
<properties>
- <help>Generate PKI certificates and keys</help>
+ <help>Generate public key infrastructure (PKI) certificates and keys</help>
</properties>
<children>
<node name="ca">
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index a6ce04624..e13270364 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -219,7 +219,7 @@
<path>firewall ipv4 forward filter rule</path>
</completionHelp>
</properties>
- <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-FWD-filter-$8-[ADRJC]\]"</command>
+ <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-FWD-filter-$8-[ADRJCO]\]"</command>
</tagNode>
</children>
</node>
@@ -322,7 +322,7 @@
<path>firewall ipv6 forward filter rule</path>
</completionHelp>
</properties>
- <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-FWD-filter-$8-[ADRJC]\]"</command>
+ <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-FWD-filter-$8-[ADRJCO]\]"</command>
</tagNode>
</children>
</node>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index ff078649d..28ccee769 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -132,6 +132,9 @@ class ConfigMgmt:
{}).get('source_address', '')
if config.exists(['system', 'host-name']):
self.hostname = config.return_value(['system', 'host-name'])
+ if config.exists(['system', 'domain-name']):
+ tmp = config.return_value(['system', 'domain-name'])
+ self.hostname += f'.{tmp}'
else:
self.hostname = 'vyos'
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index d048901f0..423fe01ed 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -20,10 +20,22 @@ from ctypes import cdll, c_char_p, c_void_p, c_int, c_bool
LIBPATH = '/usr/lib/libvyosconfig.so.0'
+def replace_backslash(s, search, replace):
+ """Modify quoted strings containing backslashes not of escape sequences"""
+ def replace_method(match):
+ result = match.group().replace(search, replace)
+ return result
+ p = re.compile(r'("[^"]*[\\][^"]*"\n|\'[^\']*[\\][^\']*\'\n)')
+ return p.sub(replace_method, s)
+
def escape_backslash(string: str) -> str:
- """Escape single backslashes in string that are not in escape sequence"""
- p = re.compile(r'(?<!\\)[\\](?!b|f|n|r|t|\\[^bfnrt])')
- result = p.sub(r'\\\\', string)
+ """Escape single backslashes in quoted strings"""
+ result = replace_backslash(string, '\\', '\\\\')
+ return result
+
+def unescape_backslash(string: str) -> str:
+ """Unescape backslashes in quoted strings"""
+ result = replace_backslash(string, '\\\\', '\\')
return result
def extract_version(s):
@@ -165,11 +177,14 @@ class ConfigTree(object):
def to_string(self, ordered_values=False):
config_string = self.__to_string(self.__config, ordered_values).decode()
+ config_string = unescape_backslash(config_string)
config_string = "{0}\n{1}".format(config_string, self.__version)
return config_string
def to_commands(self, op="set"):
- return self.__to_commands(self.__config, op.encode()).decode()
+ commands = self.__to_commands(self.__config, op.encode()).decode()
+ commands = unescape_backslash(commands)
+ return commands
def to_json(self):
return self.__to_json(self.__config).decode()
@@ -362,6 +377,7 @@ def show_diff(left, right, path=[], commands=False, libpath=LIBPATH):
msg = __get_error().decode()
raise ConfigTreeError(msg)
+ res = unescape_backslash(res)
return res
def union(left, right, libpath=LIBPATH):
diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py
index 5d3723876..6508ccdd9 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -269,14 +269,33 @@ def verify_bridge_delete(config):
raise ConfigError(f'Interface "{interface}" cannot be deleted as it '
f'is a member of bridge "{bridge_name}"!')
-def verify_interface_exists(ifname):
+def verify_interface_exists(ifname, warning_only=False):
"""
Common helper function used by interface implementations to perform
- recurring validation if an interface actually exists.
+ recurring validation if an interface actually exists. We first probe
+ if the interface is defined on the CLI, if it's not found we try if
+ it exists at the OS level.
"""
import os
- if not os.path.exists(f'/sys/class/net/{ifname}'):
- raise ConfigError(f'Interface "{ifname}" does not exist!')
+ from vyos.base import Warning
+ from vyos.configquery import ConfigTreeQuery
+ from vyos.utils.dict import dict_search_recursive
+
+ # Check if interface is present in CLI config
+ config = ConfigTreeQuery()
+ tmp = config.get_config_dict(['interfaces'], get_first_key=True)
+ if bool(list(dict_search_recursive(tmp, ifname))):
+ return True
+
+ # Interface not found on CLI, try Linux Kernel
+ if os.path.exists(f'/sys/class/net/{ifname}'):
+ return True
+
+ message = f'Interface "{ifname}" does not exist!'
+ if warning_only:
+ Warning(message)
+ return False
+ raise ConfigError(message)
def verify_source_interface(config):
"""
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 473c98d0c..5e241fc08 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -24,7 +24,6 @@ from vyos.utils.process import popen
_drivers_without_speed_duplex_flow = ['vmxnet3', 'virtio_net', 'xen_netfront',
'iavf', 'ice', 'i40e', 'hv_netvsc', 'veth', 'ixgbevf',
'tun']
-_drivers_without_eee = ['vmxnet3', 'virtio_net', 'xen_netfront', 'hv_netvsc']
class Ethtool:
"""
@@ -63,8 +62,6 @@ class Ethtool:
_auto_negotiation = False
_auto_negotiation_supported = None
_flow_control = None
- _eee = False
- _eee_enabled = None
def __init__(self, ifname):
# Get driver used for interface
@@ -118,15 +115,6 @@ class Ethtool:
if not bool(err):
self._flow_control = loads(out)
- # Get current Energy Efficient Ethernet (EEE) settings, but this is
- # not supported by all NICs (e.g. vmxnet3 does not support is)
- out, _ = popen(f'ethtool --show-eee {ifname}')
- if len(out.splitlines()) > 1:
- self._eee = True
- # read current EEE setting, this returns:
- # EEE status: disabled || EEE status: enabled - inactive || EEE status: enabled - active
- self._eee_enabled = bool('enabled' in out.splitlines()[1])
-
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
return self._auto_negotiation_supported
@@ -211,15 +199,3 @@ class Ethtool:
'flow-control settings!')
return 'on' if bool(self._flow_control[0]['autonegotiate']) else 'off'
-
- def check_eee(self):
- """ Check if the NIC supports eee """
- if self.get_driver_name() in _drivers_without_eee:
- return False
- return self._eee
-
- def get_eee(self):
- if self._eee_enabled == None:
- raise ValueError('Interface does not support changing '\
- 'EEE settings!')
- return self._eee_enabled
diff --git a/python/vyos/frr.py b/python/vyos/frr.py
index a01d967e4..c3703cbb4 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -68,6 +68,7 @@ Apply the new configuration:
import tempfile
import re
+from vyos import ConfigError
from vyos.utils.permission import chown
from vyos.utils.process import cmd
from vyos.utils.process import popen
@@ -95,6 +96,7 @@ path_config = '/run/frr'
default_add_before = r'(ip prefix-list .*|route-map .*|line vty|end)'
+
class FrrError(Exception):
pass
@@ -210,13 +212,12 @@ def reload_configuration(config, daemon=None):
LOG.debug(f'reload_configuration: Executing command against frr-reload: "{cmd}"')
output, code = popen(cmd, stderr=STDOUT)
f.close()
+
for i, e in enumerate(output.split('\n')):
LOG.debug(f'frr-reload output: {i:3} {e}')
+
if code == 1:
- raise CommitError('FRR configuration failed while running commit. Please ' \
- 'enable debugging to examine logs.\n\n\n' \
- 'To enable debugging run: "touch /tmp/vyos.frr.debug" ' \
- 'and "sudo systemctl stop vyos-configd"')
+ raise ConfigError(output)
elif code:
raise OSError(code, output)
@@ -469,17 +470,22 @@ class FRRConfig:
# https://github.com/FRRouting/frr/issues/10133
count = 0
count_max = 5
+ emsg = ''
while count < count_max:
count += 1
try:
reload_configuration('\n'.join(self.config), daemon=daemon)
break
+ except ConfigError as e:
+ emsg = str(e)
except:
# we just need to re-try the commit of the configuration
# for the listed FRR issues above
pass
if count >= count_max:
- raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} dameon!')
+ if emsg:
+ raise ConfigError(emsg)
+ raise ConfigurationNotValid(f'Config commit retry counter ({count_max}) exceeded for {daemon} daemon!')
# Save configuration to /run/frr/config/frr.conf
save_configuration()
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index b29e71394..7936e3da5 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -1,4 +1,4 @@
-# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2024 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
@@ -14,12 +14,11 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from netifaces import interfaces
-import json
from vyos.ifconfig.interface import Interface
from vyos.utils.assertion import assert_boolean
+from vyos.utils.assertion import assert_list
from vyos.utils.assertion import assert_positive
-from vyos.utils.process import cmd
from vyos.utils.dict import dict_search
from vyos.configdict import get_vlan_ids
from vyos.configdict import list_diff
@@ -86,6 +85,10 @@ class BridgeIf(Interface):
'validate': assert_boolean,
'location': '/sys/class/net/{ifname}/bridge/vlan_filtering',
},
+ 'vlan_protocol': {
+ 'validate': lambda v: assert_list(v, ['0x88a8', '0x8100']),
+ 'location': '/sys/class/net/{ifname}/bridge/vlan_protocol',
+ },
'multicast_querier': {
'validate': assert_boolean,
'location': '/sys/class/net/{ifname}/bridge/multicast_querier',
@@ -248,6 +251,26 @@ class BridgeIf(Interface):
"""
return self.set_interface('del_port', interface)
+ def set_vlan_protocol(self, protocol):
+ """
+ Set protocol used for VLAN filtering.
+ The valid values are 0x8100(802.1q) or 0x88A8(802.1ad).
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').del_port('eth1')
+ """
+
+ if protocol not in ['802.1q', '802.1ad']:
+ raise ValueError()
+
+ map = {
+ '802.1ad': '0x88a8',
+ '802.1q' : '0x8100'
+ }
+
+ return self.set_interface('vlan_protocol', map[protocol])
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -294,10 +317,13 @@ class BridgeIf(Interface):
if member in interfaces():
self.del_port(member)
- # enable/disable Vlan Filter
+ # enable/disable VLAN Filter
tmp = '1' if 'enable_vlan' in config else '0'
self.set_vlan_filter(tmp)
+ tmp = config.get('protocol')
+ self.set_vlan_protocol(tmp)
+
# add VLAN interfaces to local 'parent' bridge to allow forwarding
if 'enable_vlan' in config:
for vlan in config.get('vif_remove', {}):
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index c3f5bbf47..8d96c863f 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -404,34 +404,6 @@ class EthernetIf(Interface):
print(f'could not set "{rx_tx}" ring-buffer for {ifname}')
return output
- def set_eee(self, enable):
- """
- Enable/Disable Energy Efficient Ethernet (EEE) settings
-
- Example:
- >>> from vyos.ifconfig import EthernetIf
- >>> i = EthernetIf('eth0')
- >>> i.set_eee(enable=False)
- """
- if not isinstance(enable, bool):
- raise ValueError('Value out of range')
-
- if not self.ethtool.check_eee():
- self._debug_msg(f'NIC driver does not support changing EEE settings!')
- return False
-
- current = self.ethtool.get_eee()
- if current != enable:
- # Assemble command executed on system. Unfortunately there is no way
- # to change this setting via sysfs
- cmd = f'ethtool --set-eee {self.ifname} eee '
- cmd += 'on' if enable else 'off'
- output, code = self._popen(cmd)
- if code:
- Warning(f'could not change "{self.ifname}" EEE setting!')
- return output
- return None
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -442,9 +414,6 @@ class EthernetIf(Interface):
value = 'off' if 'disable_flow_control' in config else 'on'
self.set_flow_control(value)
- # Always disable Energy Efficient Ethernet
- self.set_eee(False)
-
# GRO (generic receive offload)
self.set_gro(dict_search('offload.gro', config) != None)
diff --git a/python/vyos/ifconfig/vti.py b/python/vyos/ifconfig/vti.py
index 9ebbeb9ed..9511386f4 100644
--- a/python/vyos/ifconfig/vti.py
+++ b/python/vyos/ifconfig/vti.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2021-2024 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
@@ -52,8 +52,14 @@ class VTIIf(Interface):
cmd += f' {iproute2_key} {tmp}'
self._cmd(cmd.format(**self.config))
+
+ # interface is always A/D down. It needs to be enabled explicitly
self.set_interface('admin_state', 'down')
+ def set_admin_state(self, state):
+ """ Handled outside by /etc/ipsec.d/vti-up-down """
+ pass
+
def get_mac(self):
""" Get a synthetic MAC address. """
return self.get_mac_synthetic()
diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py
index e1af1a682..8dab9a4ca 100644
--- a/python/vyos/opmode.py
+++ b/python/vyos/opmode.py
@@ -81,7 +81,7 @@ class InternalError(Error):
def _is_op_mode_function_name(name):
- if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set)", name):
+ if re.match(r"^(show|clear|reset|restart|add|update|delete|generate|set|renew)", name):
return True
else:
return False
diff --git a/python/vyos/priority.py b/python/vyos/priority.py
new file mode 100644
index 000000000..ab4e6d411
--- /dev/null
+++ b/python/vyos/priority.py
@@ -0,0 +1,75 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from pathlib import Path
+from typing import List
+
+from vyos.xml_ref import load_reference
+from vyos.base import Warning as Warn
+
+def priority_data(d: dict) -> list:
+ def func(d, path, res, hier):
+ for k,v in d.items():
+ if not 'node_data' in v:
+ continue
+ subpath = path + [k]
+ hier_prio = hier
+ data = v.get('node_data')
+ o = data.get('owner')
+ p = data.get('priority')
+ # a few interface-definitions have priority preceding owner
+ # attribute, instead of within properties; pass in descent
+ if p is not None and o is None:
+ hier_prio = p
+ if o is not None and p is None:
+ p = hier_prio
+ if o is not None and p is not None:
+ o = Path(o.split()[0]).name
+ p = int(p)
+ res.append((subpath, o, p))
+ if isinstance(v, dict):
+ func(v, subpath, res, hier_prio)
+ return res
+ ret = func(d, [], [], 0)
+ ret = sorted(ret, key=lambda x: x[0])
+ ret = sorted(ret, key=lambda x: x[2])
+ return ret
+
+def get_priority_data() -> list:
+ xml = load_reference()
+ return priority_data(xml.ref)
+
+def priority_sort(sections: List[list[str]] = None,
+ owners: List[str] = None,
+ reverse=False) -> List:
+ if sections is not None:
+ index = 0
+ collection: List = sections
+ elif owners is not None:
+ index = 1
+ collection = owners
+ else:
+ raise ValueError('one of sections or owners is required')
+
+ l = get_priority_data()
+ m = [item for item in l if item[index] in collection]
+ n = sorted(m, key=lambda x: x[2], reverse=reverse)
+ o = [item[index] for item in n]
+ # sections are unhashable; use comprehension
+ missed = [j for j in collection if j not in o]
+ if missed:
+ Warn(f'No priority available for elements {missed}')
+
+ return o
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 47318122b..c8e881ee2 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -324,6 +324,11 @@ class QoSBase:
if 'burst' in cls_config:
burst = cls_config['burst']
filter_cmd += f' burst {burst}'
+
+ if 'mtu' in cls_config:
+ mtu = cls_config['mtu']
+ filter_cmd += f' mtu {mtu}'
+
cls = int(cls)
filter_cmd += f' flowid {self._parent:x}:{cls:x}'
self._cmd(filter_cmd)
@@ -387,6 +392,10 @@ class QoSBase:
burst = config['default']['burst']
filter_cmd += f' burst {burst}'
+ if 'mtu' in config['default']:
+ mtu = config['default']['mtu']
+ filter_cmd += f' mtu {mtu}'
+
if 'class' in config:
filter_cmd += f' flowid {self._parent:x}:{default_cls_id:x}'
diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py
index 37b834ad6..1b487c1d2 100644
--- a/python/vyos/system/compat.py
+++ b/python/vyos/system/compat.py
@@ -198,11 +198,11 @@ def update_cfg_ver(root_dir:str = '') -> int:
return cfg_version
-def get_default(menu_entries: list, root_dir: str = '') -> Union[int, None]:
+def get_default(data: dict, root_dir: str = '') -> Union[int, None]:
"""Translate default version to menuentry index
Args:
- menu_entries (list): list of dicts of installed version boot data
+ data (dict): boot data
root_dir (str): an optional path to the root directory
Returns:
@@ -213,10 +213,22 @@ def get_default(menu_entries: list, root_dir: str = '') -> Union[int, None]:
grub_cfg_main = f'{root_dir}/{grub.GRUB_CFG_MAIN}'
+ menu_entries = data.get('versions', [])
+ console_type = data.get('console_type', 'tty')
+ console_num = data.get('console_num', '0')
image_name = image.get_default_image()
- sublist = list(filter(lambda x: x.get('version') == image_name,
- menu_entries))
+ sublist = list(filter(lambda x: (x.get('version') == image_name and
+ x.get('console_type') == console_type and
+ x.get('console_num') == console_num and
+ x.get('bootmode') == 'normal'),
+ menu_entries))
+ # legacy images added with legacy tools omitted 'ttyUSB'; if entry not
+ # available, default to initial entry of version
+ if not sublist:
+ sublist = list(filter(lambda x: x.get('version') == image_name,
+ menu_entries))
+
if sublist:
return menu_entries.index(sublist[0])
@@ -291,7 +303,7 @@ def grub_cfg_fields(root_dir: str = '') -> dict:
menu_entries = update_version_list(root_dir)
fields['versions'] = menu_entries
- default = get_default(menu_entries, root_dir)
+ default = get_default(fields, root_dir)
if default is not None:
fields['default'] = default
diff --git a/python/vyos/system/grub.py b/python/vyos/system/grub.py
index 2e8b20972..e56f0bec8 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -17,6 +17,7 @@ import platform
from pathlib import Path
from re import MULTILINE, compile as re_compile
+from shutil import copy2
from typing import Union
from uuid import uuid5, NAMESPACE_URL, UUID
@@ -373,7 +374,7 @@ def create_structure(root_dir: str = '') -> None:
if not root_dir:
root_dir = disk.find_persistence()
- Path(f'{root_dir}/GRUB_DIR_VYOS_VERS').mkdir(parents=True, exist_ok=True)
+ Path(f'{root_dir}/{GRUB_DIR_VYOS_VERS}').mkdir(parents=True, exist_ok=True)
def set_console_type(console_type: str, root_dir: str = '') -> None:
@@ -422,3 +423,38 @@ def set_kernel_cmdline_options(cmdline_options: str, version_name: str,
version_add(version_name=version_name, root_dir=root_dir,
boot_opts_config=cmdline_options)
+
+
+def sort_inodes(dir_path: str) -> None:
+ """Sort inodes for files inside a folder
+ Regenerate inodes for each file to get the same order for both inodes
+ and file names
+
+ GRUB iterates files by inodes, not alphabetically. Therefore, if we
+ want to read them in proper order, we need to sort inodes for all
+ config files in a folder.
+
+ Args:
+ dir_path (str): a path to directory
+ """
+ dir_content: list[Path] = sorted(Path(dir_path).iterdir())
+ temp_list_old: list[Path] = []
+ temp_list_new: list[Path] = []
+
+ # create a copy of all files, to get new inodes
+ for item in dir_content:
+ # skip directories
+ if item.is_dir():
+ continue
+ # create a new copy of file with a temporary name
+ copy_path = Path(f'{item.as_posix()}_tmp')
+ copy2(item, Path(copy_path))
+ temp_list_old.append(item)
+ temp_list_new.append(copy_path)
+
+ # delete old files and rename new ones
+ for item in temp_list_old:
+ item.unlink()
+ for item in temp_list_new:
+ new_name = Path(f'{item.as_posix()[0:-4]}')
+ item.rename(new_name)
diff --git a/python/vyos/template.py b/python/vyos/template.py
index 456239568..bde8e3554 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -803,8 +803,8 @@ def kea_address_json(addresses):
return dumps(out)
-@register_filter('kea_failover_json')
-def kea_failover_json(config):
+@register_filter('kea_high_availability_json')
+def kea_high_availability_json(config):
from json import dumps
source_addr = config['source_address']
diff --git a/python/vyos/xml_ref/generate_cache.py b/python/vyos/xml_ref/generate_cache.py
index 6a05d4608..d1ccb0f81 100755
--- a/python/vyos/xml_ref/generate_cache.py
+++ b/python/vyos/xml_ref/generate_cache.py
@@ -38,7 +38,8 @@ xml_tmp = join('/tmp', xml_cache_json)
pkg_cache = abspath(join(_here, 'pkg_cache'))
ref_cache = abspath(join(_here, 'cache.py'))
-node_data_fields = ("node_type", "multi", "valueless", "default_value")
+node_data_fields = ("node_type", "multi", "valueless", "default_value",
+ "owner", "priority")
def trim_node_data(cache: dict):
for k in list(cache):
diff --git a/smoketest/config-tests/dialup-router-medium-vpn b/smoketest/config-tests/dialup-router-medium-vpn
index bdae6e807..89eec0f65 100644
--- a/smoketest/config-tests/dialup-router-medium-vpn
+++ b/smoketest/config-tests/dialup-router-medium-vpn
@@ -249,10 +249,10 @@ set service conntrack-sync expect-sync 'all'
set service conntrack-sync failover-mechanism vrrp sync-group 'failover-group'
set service conntrack-sync interface eth1 peer '192.168.0.251'
set service conntrack-sync sync-queue-size '8'
-set service dhcp-server failover name 'DHCP02'
-set service dhcp-server failover remote '192.168.0.251'
-set service dhcp-server failover source-address '192.168.0.250'
-set service dhcp-server failover status 'primary'
+set service dhcp-server high-availability name 'DHCP02'
+set service dhcp-server high-availability remote '192.168.0.251'
+set service dhcp-server high-availability source-address '192.168.0.250'
+set service dhcp-server high-availability status 'primary'
set service dhcp-server shared-network-name LAN authoritative
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 lease '86400'
set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
diff --git a/smoketest/scripts/cli/test_backslash_escape.py b/smoketest/scripts/cli/test_backslash_escape.py
new file mode 100755
index 000000000..e94e9ab0a
--- /dev/null
+++ b/smoketest/scripts/cli/test_backslash_escape.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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.configtree import ConfigTree
+
+base_path = ['interfaces', 'ethernet', 'eth0', 'description']
+
+cases_word = [r'fo\o', r'fo\\o', r'foço\o', r'foço\\o']
+# legacy CLI output quotes only if whitespace present; this is a notable
+# difference that confounds the translation legacy -> modern, hence
+# determines the regex used in function replace_backslash
+cases_phrase = [r'some fo\o', r'some fo\\o', r'some foço\o', r'some foço\\o']
+
+case_save_config = '/tmp/smoketest-case-save'
+
+class TestBackslashEscape(VyOSUnitTestSHIM.TestCase):
+ def test_backslash_escape_word(self):
+ for case in cases_word:
+ self.cli_set(base_path + [case])
+ self.cli_commit()
+ # save_config tests translation though subsystems:
+ # legacy output -> config -> configtree -> file
+ self._session.save_config(case_save_config)
+ # reload to configtree and confirm:
+ with open(case_save_config) as f:
+ config_string = f.read()
+ ct = ConfigTree(config_string)
+ res = ct.return_value(base_path)
+ self.assertEqual(case, res, msg=res)
+ print(f'description: {res}')
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ def test_backslash_escape_phrase(self):
+ for case in cases_phrase:
+ self.cli_set(base_path + [case])
+ self.cli_commit()
+ # save_config tests translation though subsystems:
+ # legacy output -> config -> configtree -> file
+ self._session.save_config(case_save_config)
+ # reload to configtree and confirm:
+ with open(case_save_config) as f:
+ config_string = f.read()
+ ct = ConfigTree(config_string)
+ res = ct.return_value(base_path)
+ self.assertEqual(case, res, msg=res)
+ print(f'description: {res}')
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 9e8473fa4..fe6977252 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -598,14 +598,30 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_filter')
- def test_ipv4_state_and_status_rules(self):
- name = 'smoketest-state'
- interface = 'eth0'
-
+ def test_ipv4_global_state(self):
self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'related', 'action', 'accept'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'invalid', 'action', 'drop'])
+ self.cli_commit()
+
+ nftables_search = [
+ ['jump VYOS_STATE_POLICY'],
+ ['chain VYOS_STATE_POLICY'],
+ ['ct state established', 'accept'],
+ ['ct state invalid', 'drop'],
+ ['ct state related', 'accept']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip vyos_filter')
+
+ # Check conntrack is enabled from state-policy
+ self.verify_nftables_chain([['accept']], 'ip vyos_conntrack', 'FW_CONNTRACK')
+ self.verify_nftables_chain([['accept']], 'ip6 vyos_conntrack', 'FW_CONNTRACK')
+
+ def test_ipv4_state_and_status_rules(self):
+ name = 'smoketest-state'
+
self.cli_set(['firewall', 'ipv4', 'name', name, 'default-action', 'drop'])
self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'ipv4', 'name', name, 'rule', '1', 'state', 'established'])
@@ -632,12 +648,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ct state new', 'ct status dnat', 'accept'],
['ct state { established, new }', 'ct status snat', 'accept'],
['ct state related', 'ct helper { "ftp", "pptp" }', 'accept'],
- ['drop', f'comment "{name} default-action drop"'],
- ['jump VYOS_STATE_POLICY'],
- ['chain VYOS_STATE_POLICY'],
- ['ct state established', 'accept'],
- ['ct state invalid', 'drop'],
- ['ct state related', 'accept']
+ ['drop', f'comment "{name} default-action drop"']
]
self.verify_nftables(nftables_search, 'ip vyos_filter')
diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index 3500e97d6..124c1fbcb 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -182,6 +182,10 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
for interface in self._interfaces:
cost = 1000
priority = 10
+
+ tmp = get_interface_config(interface)
+ self.assertEqual('802.1Q', tmp['linkinfo']['info_data']['vlan_protocol']) # default VLAN protocol
+
for member in self._members:
tmp = get_interface_config(member)
self.assertEqual(interface, tmp['master'])
@@ -442,5 +446,19 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_delete(['interfaces', 'tunnel', tunnel_if])
self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr])
+ def test_bridge_vlan_protocol(self):
+ protocol = '802.1ad'
+
+ # Add member interface to bridge and set VLAN filter
+ for interface in self._interfaces:
+ self.cli_set(self._base_path + [interface, 'protocol', protocol])
+
+ # commit config
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ tmp = get_interface_config(interface)
+ self.assertEqual(protocol, tmp['linkinfo']['info_data']['vlan_protocol'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_vti.py b/smoketest/scripts/cli/test_interfaces_vti.py
index 7f13575a3..871ac650b 100755
--- a/smoketest/scripts/cli/test_interfaces_vti.py
+++ b/smoketest/scripts/cli/test_interfaces_vti.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-2024 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
@@ -18,6 +18,9 @@ import unittest
from base_interfaces_test import BasicInterfaceTest
+from vyos.ifconfig import Interface
+from vyos.utils.network import is_intf_addr_assigned
+
class VTIInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -27,5 +30,19 @@ class VTIInterfaceTest(BasicInterfaceTest.TestCase):
# call base-classes classmethod
super(VTIInterfaceTest, cls).setUpClass()
+ def test_add_single_ip_address(self):
+ addr = '192.0.2.0/31'
+ for intf in self._interfaces:
+ self.cli_set(self._base_path + [intf, 'address', addr])
+ for option in self._options.get(intf, []):
+ self.cli_set(self._base_path + [intf] + option.split())
+
+ self.cli_commit()
+
+ # VTI interface are always down and only brought up by IPSec
+ for intf in self._interfaces:
+ self.assertTrue(is_intf_addr_assigned(intf, addr))
+ self.assertEqual(Interface(intf).get_admin_state(), 'down')
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index c21d8af4e..ee4445251 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1065,6 +1065,7 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
'20' : {
'action' : 'permit',
'set' : {
+ 'as-path-exclude' : 'all',
'evpn-gateway-ipv4' : '192.0.2.99',
'evpn-gateway-ipv6' : '2001:db8:f00::1',
},
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 1d68ae08b..5f238b25a 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -1236,6 +1236,11 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' neighbor {pg_ipv6} activate', afiv6_config)
self.assertIn(f' neighbor {pg_ipv6} maximum-prefix {ipv6_max_prefix}', afiv6_config)
+ def test_bgp_27_route_reflector_client(self):
+ self.cli_set(base_path + ['peer-group', 'peer1', 'address-family', 'l2vpn-evpn', 'route-reflector-client'])
+ with self.assertRaises(ConfigSessionError) as e:
+ self.cli_commit()
+
def test_bgp_99_bmp(self):
target_name = 'instance-bmp'
target_address = '127.0.0.1'
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index aa5f2f38c..0fd18a6da 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 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
@@ -73,6 +73,12 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.isis_base_config()
+
+ self.cli_set(base_path + ['redistribute', 'ipv4', 'connected'])
+ # verify() - Redistribute level-1 or level-2 should be specified
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
self.cli_set(base_path + ['redistribute', 'ipv4', 'connected', 'level-2', 'route-map', route_map])
self.cli_set(base_path + ['log-adjacency-changes'])
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 82fb96754..1b9cc50fe 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -540,5 +540,25 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
for router_id in router_ids:
self.assertIn(f' graceful-restart helper enable {router_id}', frrconfig)
+ def test_ospf_17_duplicate_area_network(self):
+ area0 = '0'
+ area1 = '1'
+ network = '10.0.0.0/8'
+
+ self.cli_set(base_path + ['area', area0, 'network', network])
+
+ # we can not have the same network defined on two areas
+ self.cli_set(base_path + ['area', area1, 'network', network])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['area', area0])
+
+ self.cli_commit()
+
+ # Verify FRR ospfd configuration
+ frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
+ self.assertIn(f'router ospf', frrconfig)
+ self.assertIn(f' network {network} area {area1}', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index 81e7326f8..46ef68b1d 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -38,6 +38,13 @@ def get_tc_filter_json(interface, direction) -> list:
tmp = loads(tmp)
return tmp
+def get_tc_filter_details(interface, direction) -> list:
+ # json doesn't contain all params, such as mtu
+ if direction not in ['ingress', 'egress']:
+ raise ValueError()
+ tmp = cmd(f'tc -details filter show dev {interface} {direction}')
+ return tmp
+
class TestQoS(VyOSUnitTestSHIM.TestCase):
@classmethod
def setUpClass(cls):
@@ -234,7 +241,12 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
def test_05_limiter(self):
qos_config = {
'1' : {
- 'bandwidth' : '1000000',
+ 'bandwidth' : '3000000',
+ 'exceed' : 'pipe',
+ 'burst' : '100Kb',
+ 'mtu' : '1600',
+ 'not-exceed' : 'continue',
+ 'priority': '15',
'match4' : {
'ssh' : { 'dport' : '22', },
},
@@ -262,6 +274,10 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['interface', interface, 'ingress', policy_name])
# set default bandwidth parameter for all remaining connections
self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'bandwidth', '500000'])
+ self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'burst', '200kb'])
+ self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'exceed', 'drop'])
+ self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'mtu', '3000'])
+ self.cli_set(base_path + ['policy', 'limiter', policy_name, 'default', 'not-exceed', 'ok'])
for qos_class, qos_class_config in qos_config.items():
qos_class_base = base_path + ['policy', 'limiter', policy_name, 'class', qos_class]
@@ -279,6 +295,21 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
if 'bandwidth' in qos_class_config:
self.cli_set(qos_class_base + ['bandwidth', qos_class_config['bandwidth']])
+ if 'exceed' in qos_class_config:
+ self.cli_set(qos_class_base + ['exceed', qos_class_config['exceed']])
+
+ if 'not-exceed' in qos_class_config:
+ self.cli_set(qos_class_base + ['not-exceed', qos_class_config['not-exceed']])
+
+ if 'burst' in qos_class_config:
+ self.cli_set(qos_class_base + ['burst', qos_class_config['burst']])
+
+ if 'mtu' in qos_class_config:
+ self.cli_set(qos_class_base + ['mtu', qos_class_config['mtu']])
+
+ if 'priority' in qos_class_config:
+ self.cli_set(qos_class_base + ['priority', qos_class_config['priority']])
+
# commit changes
self.cli_commit()
@@ -303,6 +334,14 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
dport = int(match_config['dport'])
self.assertEqual(f'{dport:x}', filter['options']['match']['value'])
+ tc_details = get_tc_filter_details(interface, 'ingress')
+ self.assertTrue('filter parent ffff: protocol all pref 20 u32 chain 0' in tc_details)
+ self.assertTrue('rate 1Gbit burst 15125b mtu 2Kb action drop overhead 0b linklayer ethernet' in tc_details)
+ self.assertTrue('filter parent ffff: protocol all pref 15 u32 chain 0' in tc_details)
+ self.assertTrue('rate 3Gbit burst 102000b mtu 1600b action pipe/continue overhead 0b linklayer ethernet' in tc_details)
+ self.assertTrue('rate 500Mbit burst 204687b mtu 3000b action drop overhead 0b linklayer ethernet' in tc_details)
+ self.assertTrue('filter parent ffff: protocol all pref 255 basic chain 0' in tc_details)
+
def test_06_network_emulator(self):
policy_type = 'network-emulator'
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index b582a2108..24bd14af2 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -673,7 +673,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
- def test_dhcp_failover(self):
+ def test_dhcp_high_availability(self):
shared_net_name = 'FAILOVER'
failover_name = 'VyOS-Failover'
@@ -695,10 +695,10 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
failover_local = router
failover_remote = inc_ip(router, 1)
- self.cli_set(base_path + ['failover', 'source-address', failover_local])
- self.cli_set(base_path + ['failover', 'name', failover_name])
- self.cli_set(base_path + ['failover', 'remote', failover_remote])
- self.cli_set(base_path + ['failover', 'status', 'primary'])
+ self.cli_set(base_path + ['high-availability', 'source-address', failover_local])
+ self.cli_set(base_path + ['high-availability', 'name', failover_name])
+ self.cli_set(base_path + ['high-availability', 'remote', failover_remote])
+ self.cli_set(base_path + ['high-availability', 'status', 'primary'])
# commit changes
self.cli_commit()
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 5fc2019fd..d1ff25a58 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -195,6 +195,34 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
for src in ra_src:
self.assertIn(f' {src};', config)
+ def test_nat64prefix(self):
+ nat64prefix = '64:ff9b::/96'
+ nat64prefix_invalid = '64:ff9b::/44'
+
+ self.cli_set(base_path + ['nat64prefix', nat64prefix])
+
+ # and another invalid prefix
+ # Invalid NAT64 prefix length for "2001:db8::/34", can only be one of:
+ # /32, /40, /48, /56, /64, /96
+ self.cli_set(base_path + ['nat64prefix', nat64prefix_invalid])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['nat64prefix', nat64prefix_invalid])
+
+ # NAT64 valid-lifetime must not be smaller then "interval max"
+ self.cli_set(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime', '500'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['nat64prefix', nat64prefix, 'valid-lifetime'])
+
+ # commit changes
+ self.cli_commit()
+
+ config = read_file(RADVD_CONF)
+
+ tmp = f'nat64prefix {nat64prefix}' + ' {'
+ self.assertIn(tmp, config)
+ self.assertIn('AdvValidLifetime 65528;', config) # default
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index f00626b3d..2d76da145 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -174,12 +174,16 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
'pptp': {
'driver': ['nf_nat_pptp', 'nf_conntrack_pptp'],
'nftables': ['ct helper set "pptp_tcp"']
- },
+ },
+ 'rtsp': {
+ 'driver': ['nf_nat_rtsp', 'nf_conntrack_rtsp'],
+ 'nftables': ['ct helper set "rtsp_tcp"']
+ },
'sip': {
'driver': ['nf_nat_sip', 'nf_conntrack_sip'],
'nftables': ['ct helper set "sip_tcp"',
'ct helper set "sip_udp"']
- },
+ },
'sqlnet': {
'nftables': ['ct helper set "tns_tcp"']
},
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index 3c27655b0..810437dda 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -268,6 +268,18 @@ def verify_rule(firewall, rule_conf, ipv6):
if 'port' in side_conf and dict_search_args(side_conf, 'group', 'port_group'):
raise ConfigError(f'{side} port-group and port cannot both be defined')
+ if 'add_address_to_group' in rule_conf:
+ for type in ['destination_address', 'source_address']:
+ if type in rule_conf['add_address_to_group']:
+ if 'address_group' not in rule_conf['add_address_to_group'][type]:
+ raise ConfigError(f'Dynamic address group must be defined.')
+ else:
+ target = rule_conf['add_address_to_group'][type]['address_group']
+ fwall_group = 'ipv6_address_group' if ipv6 else 'address_group'
+ group_obj = dict_search_args(firewall, 'group', 'dynamic_group', fwall_group, target)
+ if group_obj is None:
+ raise ConfigError(f'Invalid dynamic address group on firewall rule')
+
if 'log_options' in rule_conf:
if 'log' not in rule_conf:
raise ConfigError('log-options defined, but log is not enable')
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index d90dfe45b..f1c59cbde 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -457,6 +457,8 @@ def verify(bgp):
peer_group_as = dict_search(f'peer_group.{peer_group}.remote_as', bgp)
if peer_group_as != None and peer_group_as != 'internal' and peer_group_as != bgp['system_as']:
raise ConfigError('route-reflector-client only supported for iBGP peers')
+ else:
+ raise ConfigError('route-reflector-client only supported for iBGP peers')
# Throw an error if a peer group is not configured for allow range
for prefix in dict_search('listen.range', bgp) or []:
diff --git a/src/conf_mode/protocols_isis.py b/src/conf_mode/protocols_isis.py
index 8d594bb68..6c9925b80 100755
--- a/src/conf_mode/protocols_isis.py
+++ b/src/conf_mode/protocols_isis.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2022 VyOS maintainers and contributors
+# Copyright (C) 2020-2024 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
@@ -155,12 +155,12 @@ def verify(isis):
for proto, proto_config in isis['redistribute'][afi].items():
if 'level_1' not in proto_config and 'level_2' not in proto_config:
raise ConfigError(f'Redistribute level-1 or level-2 should be specified in ' \
- f'"protocols isis {process} redistribute {afi} {proto}"!')
+ f'"protocols isis redistribute {afi} {proto}"!')
for redistr_level, redistr_config in proto_config.items():
if proc_level and proc_level != 'level_1_2' and proc_level != redistr_level:
- raise ConfigError(f'"protocols isis {process} redistribute {afi} {proto} {redistr_level}" ' \
- f'can not be used with \"protocols isis {process} level {proc_level}\"')
+ raise ConfigError(f'"protocols isis redistribute {afi} {proto} {redistr_level}" ' \
+ f'can not be used with \"protocols isis level {proc_level}\"!')
# Segment routing checks
if dict_search('segment_routing.global_block', isis):
@@ -220,8 +220,8 @@ def verify(isis):
if ("explicit_null" in prefix_config['index']) and ("no_php_flag" in prefix_config['index']):
raise ConfigError(f'Segment routing prefix {prefix} cannot have both explicit-null '\
f'and no-php-flag configured at the same time.')
-
- # Check for index ranges being larger than the segment routing global block
+
+ # Check for index ranges being larger than the segment routing global block
if dict_search('segment_routing.global_block', isis):
g_high_label_value = dict_search('segment_routing.global_block.high_label_value', isis)
g_low_label_value = dict_search('segment_routing.global_block.low_label_value', isis)
@@ -233,7 +233,7 @@ def verify(isis):
if int(index_size) > int(g_label_difference):
raise ConfigError(f'Segment routing prefix {prefix} cannot have an '\
f'index base size larger than the SRGB label base.')
-
+
# Check for LFA tiebreaker index duplication
if dict_search('fast_reroute.lfa.local.tiebreaker', isis):
comparison_dictionary = {}
@@ -311,4 +311,4 @@ if __name__ == '__main__':
apply(c)
except ConfigError as e:
print(e)
- exit(1) \ No newline at end of file
+ exit(1)
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 695842795..6fffe7e0d 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.py
@@ -127,6 +127,7 @@ def verify(ospf):
# Validate if configured Access-list exists
if 'area' in ospf:
+ networks = []
for area, area_config in ospf['area'].items():
if 'import_list' in area_config:
acl_import = area_config['import_list']
@@ -135,6 +136,12 @@ def verify(ospf):
acl_export = area_config['export_list']
if acl_export: verify_access_list(acl_export, ospf)
+ if 'network' in area_config:
+ for network in area_config['network']:
+ if network in networks:
+ raise ConfigError(f'Network "{network}" already defined in different area!')
+ networks.append(network)
+
if 'interface' in ospf:
for interface, interface_config in ospf['interface'].items():
verify_interface_exists(interface)
diff --git a/src/conf_mode/service_dhcp-server.py b/src/conf_mode/service_dhcp-server.py
index 91ea354b6..ba3d69b07 100755
--- a/src/conf_mode/service_dhcp-server.py
+++ b/src/conf_mode/service_dhcp-server.py
@@ -143,7 +143,7 @@ def get_config(config=None):
dhcp['shared_network_name'][network]['subnet'][subnet].update(
{'range' : new_range_dict})
- if dict_search('failover.certificate', dhcp):
+ if dict_search('high_availability.certificate', dhcp):
dhcp['pki'] = conf.get_config_dict(['pki'], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True)
return dhcp
@@ -286,34 +286,34 @@ def verify(dhcp):
if (shared_networks - disabled_shared_networks) < 1:
raise ConfigError(f'At least one shared network must be active!')
- if 'failover' in dhcp:
+ if 'high_availability' in dhcp:
for key in ['name', 'remote', 'source_address', 'status']:
- if key not in dhcp['failover']:
+ if key not in dhcp['high_availability']:
tmp = key.replace('_', '-')
- raise ConfigError(f'DHCP failover requires "{tmp}" to be specified!')
+ raise ConfigError(f'DHCP high-availability requires "{tmp}" to be specified!')
- if len({'certificate', 'ca_certificate'} & set(dhcp['failover'])) == 1:
- raise ConfigError(f'DHCP secured failover requires both certificate and CA certificate')
+ if len({'certificate', 'ca_certificate'} & set(dhcp['high_availability'])) == 1:
+ raise ConfigError(f'DHCP secured high-availability requires both certificate and CA certificate')
- if 'certificate' in dhcp['failover']:
- cert_name = dhcp['failover']['certificate']
+ if 'certificate' in dhcp['high_availability']:
+ cert_name = dhcp['high_availability']['certificate']
if cert_name not in dhcp['pki']['certificate']:
- raise ConfigError(f'Invalid certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'certificate'):
- raise ConfigError(f'Invalid certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['certificate'], cert_name, 'private', 'key'):
- raise ConfigError(f'Missing private key on certificate specified for DHCP failover')
+ raise ConfigError(f'Missing private key on certificate specified for DHCP high-availability')
- if 'ca_certificate' in dhcp['failover']:
- ca_cert_name = dhcp['failover']['ca_certificate']
+ if 'ca_certificate' in dhcp['high_availability']:
+ ca_cert_name = dhcp['high_availability']['ca_certificate']
if ca_cert_name not in dhcp['pki']['ca']:
- raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability')
if not dict_search_args(dhcp['pki']['ca'], ca_cert_name, 'certificate'):
- raise ConfigError(f'Invalid CA certificate specified for DHCP failover')
+ raise ConfigError(f'Invalid CA certificate specified for DHCP high-availability')
for address in (dict_search('listen_address', dhcp) or []):
if is_addr_assigned(address):
@@ -359,23 +359,23 @@ def generate(dhcp):
if os.path.exists(f):
os.unlink(f)
- if 'failover' in dhcp:
- if 'certificate' in dhcp['failover']:
- cert_name = dhcp['failover']['certificate']
+ if 'high_availability' in dhcp:
+ if 'certificate' in dhcp['high_availability']:
+ cert_name = dhcp['high_availability']['certificate']
cert_data = dhcp['pki']['certificate'][cert_name]['certificate']
key_data = dhcp['pki']['certificate'][cert_name]['private']['key']
write_file(cert_file, wrap_certificate(cert_data), user=user_group, mode=0o600)
write_file(cert_key_file, wrap_private_key(key_data), user=user_group, mode=0o600)
- dhcp['failover']['cert_file'] = cert_file
- dhcp['failover']['cert_key_file'] = cert_key_file
+ dhcp['high_availability']['cert_file'] = cert_file
+ dhcp['high_availability']['cert_key_file'] = cert_key_file
- if 'ca_certificate' in dhcp['failover']:
- ca_cert_name = dhcp['failover']['ca_certificate']
+ if 'ca_certificate' in dhcp['high_availability']:
+ ca_cert_name = dhcp['high_availability']['ca_certificate']
ca_cert_data = dhcp['pki']['ca'][ca_cert_name]['certificate']
write_file(ca_cert_file, wrap_certificate(ca_cert_data), user=user_group, mode=0o600)
- dhcp['failover']['ca_cert_file'] = ca_cert_file
+ dhcp['high_availability']['ca_cert_file'] = ca_cert_file
render(systemd_override, 'dhcp-server/10-override.conf.j2', dhcp)
@@ -402,7 +402,7 @@ def apply(dhcp):
if service == 'kea-dhcp-ddns-server' and 'dynamic_dns_update' not in dhcp:
action = 'stop'
- if service == 'kea-ctrl-agent' and 'failover' not in dhcp:
+ if service == 'kea-ctrl-agent' and 'high_availability' not in dhcp:
action = 'stop'
call(f'systemctl {action} {service}.service')
diff --git a/src/conf_mode/service_router-advert.py b/src/conf_mode/service_router-advert.py
index dbb47de4e..88d767bb8 100755
--- a/src/conf_mode/service_router-advert.py
+++ b/src/conf_mode/service_router-advert.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2022 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 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,6 +17,8 @@
import os
from sys import exit
+from ipaddress import IPv6Network
+
from vyos.base import Warning
from vyos.config import Config
from vyos.template import render
@@ -47,7 +49,9 @@ def verify(rtradv):
return None
for interface, interface_config in rtradv['interface'].items():
- if 'prefix' in interface:
+ interval_max = int(interface_config['interval']['max'])
+
+ if 'prefix' in interface_config:
for prefix, prefix_config in interface_config['prefix'].items():
valid_lifetime = prefix_config['valid_lifetime']
if valid_lifetime == 'infinity':
@@ -60,6 +64,15 @@ def verify(rtradv):
if not (int(valid_lifetime) >= int(preferred_lifetime)):
raise ConfigError('Prefix valid-lifetime must be greater then or equal to preferred-lifetime')
+ if 'nat64prefix' in interface_config:
+ nat64_supported_lengths = [32, 40, 48, 56, 64, 96]
+ for prefix, prefix_config in interface_config['nat64prefix'].items():
+ if IPv6Network(prefix).prefixlen not in nat64_supported_lengths:
+ raise ConfigError(f'Invalid NAT64 prefix length for "{prefix}", can only be one of: /' + ', /'.join(nat64_supported_lengths))
+
+ if int(prefix_config['valid_lifetime']) < interval_max:
+ raise ConfigError(f'NAT64 valid-lifetime must not be smaller then "interval max" which is "{interval_max}"!')
+
if 'name_server' in interface_config:
if len(interface_config['name_server']) > 3:
raise ConfigError('No more then 3 IPv6 name-servers supported!')
@@ -72,7 +85,6 @@ def verify(rtradv):
# ensure stale RDNSS info gets removed in a timely fashion, this
# should not be greater than 2*MaxRtrAdvInterval.
lifetime = int(interface_config['name_server_lifetime'])
- interval_max = int(interface_config['interval']['max'])
if lifetime > 0:
if lifetime < int(interval_max):
raise ConfigError(f'RDNSS lifetime must be at least "{interval_max}" seconds!')
diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py
index e075bc928..3d42389f6 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -42,33 +42,38 @@ nftables_ct_file = r'/run/nftables-ct.conf'
module_map = {
'ftp': {
'ko': ['nf_nat_ftp', 'nf_conntrack_ftp'],
- 'nftables': ['ct helper set "ftp_tcp" tcp dport {21} return']
+ 'nftables': ['tcp dport {21} ct helper set "ftp_tcp" return']
},
'h323': {
'ko': ['nf_nat_h323', 'nf_conntrack_h323'],
- 'nftables': ['ct helper set "ras_udp" udp dport {1719} return',
- 'ct helper set "q931_tcp" tcp dport {1720} return']
+ 'nftables': ['udp dport {1719} ct helper set "ras_udp" return',
+ 'tcp dport {1720} ct helper set "q931_tcp" return']
},
'nfs': {
- 'nftables': ['ct helper set "rpc_tcp" tcp dport {111} return',
- 'ct helper set "rpc_udp" udp dport {111} return']
+ 'nftables': ['tcp dport {111} ct helper set "rpc_tcp" return',
+ 'udp dport {111} ct helper set "rpc_udp" return']
},
'pptp': {
'ko': ['nf_nat_pptp', 'nf_conntrack_pptp'],
- 'nftables': ['ct helper set "pptp_tcp" tcp dport {1723} return'],
+ 'nftables': ['tcp dport {1723} ct helper set "pptp_tcp" return'],
'ipv4': True
},
+ 'rtsp': {
+ 'ko': ['nf_nat_rtsp', 'nf_conntrack_rtsp'],
+ 'nftables': ['tcp dport {554} ct helper set "rtsp_tcp" return'],
+ 'ipv4': True
+ },
'sip': {
'ko': ['nf_nat_sip', 'nf_conntrack_sip'],
- 'nftables': ['ct helper set "sip_tcp" tcp dport {5060,5061} return',
- 'ct helper set "sip_udp" udp dport {5060,5061} return']
+ 'nftables': ['tcp dport {5060,5061} ct helper set "sip_tcp" return',
+ 'udp dport {5060,5061} ct helper set "sip_udp" return']
},
'sqlnet': {
- 'nftables': ['ct helper set "tns_tcp" tcp dport {1521,1525,1536} return']
+ 'nftables': ['tcp dport {1521,1525,1536} ct helper set "tns_tcp" return']
},
'tftp': {
'ko': ['nf_nat_tftp', 'nf_conntrack_tftp'],
- 'nftables': ['ct helper set "tftp_udp" udp dport {69} return']
+ 'nftables': ['udp dport {69} ct helper set "tftp_udp" return']
},
}
@@ -180,12 +185,16 @@ def generate(conntrack):
conntrack['ipv4_firewall_action'] = 'return'
conntrack['ipv6_firewall_action'] = 'return'
- for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
- if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
- if path[0] == 'ipv4':
- conntrack['ipv4_firewall_action'] = 'accept'
- elif path[0] == 'ipv6':
- conntrack['ipv6_firewall_action'] = 'accept'
+ if dict_search_args(conntrack['firewall'], 'global_options', 'state_policy') != None:
+ conntrack['ipv4_firewall_action'] = 'accept'
+ conntrack['ipv6_firewall_action'] = 'accept'
+ else:
+ for rules, path in dict_search_recursive(conntrack['firewall'], 'rule'):
+ if any(('state' in rule_conf or 'connection_status' in rule_conf or 'offload_target' in rule_conf) for rule_conf in rules.values()):
+ if path[0] == 'ipv4':
+ conntrack['ipv4_firewall_action'] = 'accept'
+ elif path[0] == 'ipv6':
+ conntrack['ipv6_firewall_action'] = 'accept'
render(conntrack_config, 'conntrack/vyos_nf_conntrack.conf.j2', conntrack)
render(sysctl_file, 'conntrack/sysctl.conf.j2', conntrack)
@@ -195,7 +204,7 @@ def generate(conntrack):
def apply(conntrack):
# Depending on the enable/disable state of the ALG (Application Layer Gateway)
# modules we need to either insmod or rmmod the helpers.
-
+
add_modules = []
rm_modules = []
diff --git a/src/etc/ipsec.d/vti-up-down b/src/etc/ipsec.d/vti-up-down
index 441b316c2..01e9543c9 100755
--- a/src/etc/ipsec.d/vti-up-down
+++ b/src/etc/ipsec.d/vti-up-down
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021-2023 VyOS maintainers and contributors
+# Copyright (C) 2021-2024 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
@@ -57,7 +57,9 @@ if __name__ == '__main__':
if 'disable' not in vti:
tmp = VTIIf(interface)
tmp.update(vti)
+ call(f'sudo ip link set {interface} up')
else:
+ call(f'sudo ip link set {interface} down')
syslog(f'Interface {interface} is admin down ...')
elif verb in ['down-client', 'down-host']:
if vti_link_up:
diff --git a/src/helpers/priority.py b/src/helpers/priority.py
new file mode 100755
index 000000000..04186104c
--- /dev/null
+++ b/src/helpers/priority.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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 sys
+from argparse import ArgumentParser
+from tabulate import tabulate
+
+from vyos.priority import get_priority_data
+
+if __name__ == '__main__':
+ parser = ArgumentParser()
+ parser.add_argument('--legacy-format', action='store_true',
+ help="format output for comparison with legacy 'priority.pl'")
+ args = parser.parse_args()
+
+ prio_list = get_priority_data()
+ if args.legacy_format:
+ for p in prio_list:
+ print(f'{p[2]} {"/".join(p[0])}')
+ sys.exit(0)
+
+ l = []
+ for p in prio_list:
+ l.append((p[2], p[1], p[0]))
+ headers = ['priority', 'owner', 'path']
+ out = tabulate(l, headers, numalign='right')
+ print(out)
diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py
index 7cfa8fe88..77f7cd810 100755
--- a/src/helpers/vyos_config_sync.py
+++ b/src/helpers/vyos_config_sync.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# Copyright (C) 2023-2024 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
@@ -60,19 +60,20 @@ def post_request(url: str,
return response
-def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]:
+
+def retrieve_config(section: Optional[List[str]] = None) -> Optional[Dict[str, Any]]:
"""Retrieves the configuration from the local server.
Args:
- section: str: The section of the configuration to retrieve. Default is None.
+ section: List[str]: The section of the configuration to retrieve.
+ Default is None.
Returns:
- Optional[Dict[str, Any]]: The retrieved configuration as a dictionary, or None if an error occurred.
+ Optional[Dict[str, Any]]: The retrieved configuration as a
+ dictionary, or None if an error occurred.
"""
if section is None:
section = []
- else:
- section = section.split()
conf = Config()
config = conf.get_config_dict(section, get_first_key=True)
@@ -84,25 +85,21 @@ def retrieve_config(section: str = None) -> Optional[Dict[str, Any]]:
def set_remote_config(
address: str,
key: str,
- op: str,
- path: str = None,
- section: Optional[str] = None) -> Optional[Dict[str, Any]]:
+ commands: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Loads the VyOS configuration in JSON format to a remote host.
Args:
address (str): The address of the remote host.
key (str): The key to use for loading the configuration.
- path (Optional[str]): The path of the configuration. Default is None.
- section (Optional[str]): The section of the configuration to load. Default is None.
+ commands (list): List of set/load commands for request, given as:
+ [{'op': str, 'path': list[str], 'section': dict},
+ ...]
Returns:
- Optional[Dict[str, Any]]: The response from the remote host as a dictionary, or None if an error occurred.
+ Optional[Dict[str, Any]]: The response from the remote host as a
+ dictionary, or None if a RequestException occurred.
"""
- if path is None:
- path = []
- else:
- path = path.split()
headers = {'Content-Type': 'application/json'}
# Disable the InsecureRequestWarning
@@ -110,9 +107,7 @@ def set_remote_config(
url = f'https://{address}/configure-section'
data = json.dumps({
- 'op': mode,
- 'path': path,
- 'section': section,
+ 'commands': commands,
'key': key
})
@@ -125,19 +120,18 @@ def set_remote_config(
return None
-def is_section_revised(section: str) -> bool:
+def is_section_revised(section: List[str]) -> bool:
from vyos.config_mgmt import is_node_revised
- return is_node_revised([section])
+ return is_node_revised(section)
def config_sync(secondary_address: str,
secondary_key: str,
- sections: List[str],
+ sections: List[list[str]],
mode: str):
"""Retrieve a config section from primary router in JSON format and send it to
secondary router
"""
- # Config sync only if sections changed
if not any(map(is_section_revised, sections)):
return
@@ -146,21 +140,25 @@ def config_sync(secondary_address: str,
)
# Sync sections ("nat", "firewall", etc)
+ commands = []
for section in sections:
config_json = retrieve_config(section=section)
# Check if config path deesn't exist, for example "set nat"
# we set empty value for config_json data
# As we cannot send to the remote host section "nat None" config
if not config_json:
- config_json = ""
+ config_json = {}
logger.debug(
f"Retrieved config for section '{section}': {config_json}")
- set_config = set_remote_config(address=secondary_address,
- key=secondary_key,
- op=mode,
- path=section,
- section=config_json)
- logger.debug(f"Set config for section '{section}': {set_config}")
+
+ d = {'op': mode, 'path': section, 'section': config_json}
+ commands.append(d)
+
+ set_config = set_remote_config(address=secondary_address,
+ key=secondary_key,
+ commands=commands)
+
+ logger.debug(f"Set config for sections '{sections}': {set_config}")
if __name__ == '__main__':
@@ -188,5 +186,17 @@ if __name__ == '__main__':
"Missing required configuration data for config synchronization.")
exit(0)
+ # Generate list_sections of sections/subsections
+ # [
+ # ['interfaces', 'pseudo-ethernet'], ['interfaces', 'virtual-ethernet'], ['nat'], ['nat66']
+ # ]
+ list_sections = []
+ for section, subsections in sections.items():
+ if subsections:
+ for subsection in subsections:
+ list_sections.append([section, subsection])
+ else:
+ list_sections.append([section])
+
config_sync(secondary_address, secondary_key,
- sections, mode)
+ list_sections, mode)
diff --git a/src/migration-scripts/dhcp-server/10-to-11 b/src/migration-scripts/dhcp-server/10-to-11
new file mode 100755
index 000000000..a0dc96ad0
--- /dev/null
+++ b/src/migration-scripts/dhcp-server/10-to-11
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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/>.
+
+# T6171: rename "service dhcp-server failover" to "service dhcp-server high-availability"
+
+from sys import argv
+from sys import exit
+
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcp-server']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ exit(0)
+
+if config.exists(base + ['failover']):
+ config.rename(base + ['failover'],'high-availability')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1) \ No newline at end of file
diff --git a/src/migration-scripts/dhcpv6-server/4-to-5 b/src/migration-scripts/dhcpv6-server/4-to-5
index ae506b9c5..55fda91b3 100755
--- a/src/migration-scripts/dhcpv6-server/4-to-5
+++ b/src/migration-scripts/dhcpv6-server/4-to-5
@@ -42,8 +42,11 @@ def find_subnet_interface(subnet):
def check_addr(if_path):
if config.exists(if_path + ['address']):
for addr in config.return_values(if_path + ['address']):
- if ip_network(addr, strict=False) == subnet_net:
- return True
+ try:
+ if ip_network(addr, strict=False) == subnet_net:
+ return True
+ except:
+ pass # interface address was probably "dhcp" or other magic string
return None
for iftype in config.list_nodes(['interfaces']):
diff --git a/src/migration-scripts/policy/1-to-2 b/src/migration-scripts/policy/1-to-2
index c70490ce9..c7a983bba 100755
--- a/src/migration-scripts/policy/1-to-2
+++ b/src/migration-scripts/policy/1-to-2
@@ -32,23 +32,23 @@ file_name = argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-base = ['policy', 'ipv6-route']
+base = ['policy']
config = ConfigTree(config_file)
if not config.exists(base):
# Nothing to do
exit(0)
-config.rename(base, 'route6')
-config.set_tag(['policy', 'route6'])
+if config.exists(base + ['ipv6-route']):
+ config.rename(base + ['ipv6-route'],'route6')
+ config.set_tag(['policy', 'route6'])
for route in ['route', 'route6']:
- route_path = ['policy', route]
- if config.exists(route_path):
- for name in config.list_nodes(route_path):
- if config.exists(route_path + [name, 'rule']):
- for rule in config.list_nodes(route_path + [name, 'rule']):
- rule_tcp_flags = route_path + [name, 'rule', rule, 'tcp', 'flags']
+ if config.exists(base + [route]):
+ for name in config.list_nodes(base + [route]):
+ if config.exists(base + [route, name, 'rule']):
+ for rule in config.list_nodes(base + [route, name, 'rule']):
+ rule_tcp_flags = base + [route, name, 'rule', rule, 'tcp', 'flags']
if config.exists(rule_tcp_flags):
tmp = config.return_value(rule_tcp_flags)
diff --git a/src/migration-scripts/policy/3-to-4 b/src/migration-scripts/policy/3-to-4
index 1ebb248b0..476fa3af2 100755
--- a/src/migration-scripts/policy/3-to-4
+++ b/src/migration-scripts/policy/3-to-4
@@ -51,7 +51,7 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool:
:rtype: bool
"""
community_list = list((config.return_value(rule)).split(" "))
-
+ config.delete(rule)
if 'none' in community_list:
config.set(rule + ['none'])
return False
@@ -61,10 +61,8 @@ def community_migrate(config: ConfigTree, rule: list[str]) -> bool:
community_action = 'add'
community_list.remove('additive')
for community in community_list:
- if len(community):
- config.set(rule + [community_action], value=community,
- replace=False)
- config.delete(rule)
+ config.set(rule + [community_action], value=community,
+ replace=False)
if community_action == 'replace':
return False
else:
diff --git a/src/op_mode/conntrack.py b/src/op_mode/conntrack.py
index cf8adf795..6ea213bec 100755
--- a/src/op_mode/conntrack.py
+++ b/src/op_mode/conntrack.py
@@ -112,7 +112,8 @@ def get_formatted_output(dict_data):
proto = meta['layer4']['protoname']
if direction == 'independent':
conn_id = meta['id']
- timeout = meta['timeout']
+ # T6138 flowtable offload conntrack entries without 'timeout'
+ timeout = meta.get('timeout', 'n/a')
orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src
orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
diff --git a/src/op_mode/dhcp.py b/src/op_mode/dhcp.py
index d27e1baf7..a2f947400 100755
--- a/src/op_mode/dhcp.py
+++ b/src/op_mode/dhcp.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022-2023 VyOS maintainers and contributors
+# Copyright (C) 2022-2024 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
@@ -33,6 +33,7 @@ from vyos.kea import kea_get_leases
from vyos.kea import kea_get_pool_from_subnet_id
from vyos.kea import kea_delete_lease
from vyos.utils.process import is_systemd_service_running
+from vyos.utils.process import call
time_string = "%a %b %d %H:%M:%S %Z %Y"
@@ -309,6 +310,25 @@ def _verify(func):
return func(*args, **kwargs)
return _wrapper
+def _verify_client(func):
+ """Decorator checks if interface is configured as DHCP client"""
+ from functools import wraps
+ from vyos.ifconfig import Section
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ family = kwargs.get('family')
+ v = 'v6' if family == 'inet6' else ''
+ interface = kwargs.get('interface')
+ interface_path = Section.get_config_path(interface)
+ unconf_message = f'DHCP{v} client not configured on interface {interface}!'
+
+ # Check if config does not exist
+ if not config.exists(f'interfaces {interface_path} address dhcp{v}'):
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+ return _wrapper
@_verify
def show_pool_statistics(raw: bool, family: ArgFamily, pool: typing.Optional[str]):
@@ -474,6 +494,16 @@ def show_client_leases(raw: bool, family: ArgFamily, interface: typing.Optional[
else:
return _get_formatted_client_leases(lease_data, family=family)
+@_verify_client
+def renew_client_lease(raw: bool, family: ArgFamily, interface: str):
+ if not raw:
+ v = 'v6' if family == 'inet6' else ''
+ print(f'Restarting DHCP{v} client on interface {interface}...')
+ if family == 'inet6':
+ call(f'systemctl restart dhcp6c@{interface}.service')
+ else:
+ call(f'systemctl restart dhclient@{interface}.service')
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index 85ebd19ba..b0567305a 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -786,6 +786,10 @@ def install_image() -> None:
grub.install(install_target.name, f'{DIR_DST_ROOT}/boot/',
f'{DIR_DST_ROOT}/boot/efi')
+ # sort inodes (to make GRUB read config files in alphabetical order)
+ grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS}')
+ grub.sort_inodes(f'{DIR_DST_ROOT}/{grub.GRUB_DIR_VYOS_VERS}')
+
# umount filesystems and remove temporary files
if is_raid_install(install_target):
cleanup([install_target.name],
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index a7b14a1a3..77870a84c 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -463,7 +463,7 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
endpoint = request.url.path
# Allow users to pass just one command
- if not isinstance(data, ConfigureListModel):
+ if not isinstance(data, (ConfigureListModel, ConfigSectionListModel)):
data = [data]
else:
data = data.commands
diff --git a/src/system/grub_update.py b/src/system/grub_update.py
index 5a7d8eb72..5a0534195 100644
--- a/src/system/grub_update.py
+++ b/src/system/grub_update.py
@@ -105,4 +105,8 @@ if __name__ == '__main__':
else:
render(grub_cfg_main, grub.TMPL_GRUB_MAIN, {})
+ # sort inodes (to make GRUB read config files in alphabetical order)
+ grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS}')
+ grub.sort_inodes(f'{root_dir}/{grub.GRUB_DIR_VYOS_VERS}')
+
exit(0)
diff --git a/src/systemd/dhclient@.service b/src/systemd/dhclient@.service
index 099f7ed52..d430d8868 100644
--- a/src/systemd/dhclient@.service
+++ b/src/systemd/dhclient@.service
@@ -3,6 +3,7 @@ Description=DHCP client on %i
Documentation=man:dhclient(8)
StartLimitIntervalSec=0
After=vyos-router.service
+ConditionPathExists=/run/dhclient/dhclient_%i.conf
[Service]
Type=exec
diff --git a/src/systemd/vyos-grub-update.service b/src/systemd/vyos-grub-update.service
index 522b13a33..7b67ae1b8 100644
--- a/src/systemd/vyos-grub-update.service
+++ b/src/systemd/vyos-grub-update.service
@@ -6,9 +6,9 @@ Before=vyos-router.service
[Service]
Type=oneshot
ExecStart=/usr/libexec/vyos/system/grub_update.py
-TimeoutSec=5
+TimeoutSec=60
KillMode=process
StandardOutput=journal+console
[Install]
-WantedBy=vyos-router.service \ No newline at end of file
+WantedBy=vyos-router.service