summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/config-mode-dependencies/vyos-1x.json6
-rw-r--r--data/templates/conntrack/nftables-ct.j236
-rw-r--r--data/templates/conntrackd/conntrackd.conf.j22
-rw-r--r--data/templates/frr/ospf6d.frr.j22
-rw-r--r--data/templates/frr/ospfd.frr.j28
-rw-r--r--data/templates/high-availability/keepalived.conf.j26
-rw-r--r--data/templates/load-balancing/wlb.conf.j24
-rw-r--r--data/templates/login/default_motd.j214
-rw-r--r--data/templates/vyos-hostsd/hosts.j22
-rw-r--r--data/vyos-firewall-init.conf2
-rw-r--r--interface-definitions/high-availability.xml.in1
-rw-r--r--interface-definitions/include/accel-ppp/extended-scripts.xml.i8
-rw-r--r--interface-definitions/include/firewall/common-rule-bridge.xml.i7
-rw-r--r--interface-definitions/include/firewall/common-rule-inet.xml.i7
-rw-r--r--interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i7
-rw-r--r--interface-definitions/include/ospfv3/protocol-common-config.xml.i34
-rw-r--r--interface-definitions/include/pki/cli-private-key-base64.xml.i2
-rw-r--r--interface-definitions/include/pki/cli-public-key-base64.xml.i6
-rw-r--r--interface-definitions/include/version/dhcp-server-version.xml.i2
-rw-r--r--interface-definitions/interfaces_wireless.xml.in8
-rw-r--r--interface-definitions/service_conntrack-sync.xml.in6
-rw-r--r--interface-definitions/service_dhcp-server.xml.in6
-rw-r--r--interface-definitions/system_option.xml.in6
-rw-r--r--op-mode-definitions/container.xml.in1
-rw-r--r--op-mode-definitions/pki.xml.in106
-rw-r--r--python/vyos/configdep.py53
-rw-r--r--python/vyos/ethtool.py116
-rw-r--r--python/vyos/firewall.py22
-rw-r--r--python/vyos/kea.py3
-rw-r--r--python/vyos/nat.py11
-rw-r--r--python/vyos/qos/base.py16
-rw-r--r--python/vyos/remote.py16
-rw-r--r--smoketest/config-tests/basic-vyos69
-rw-r--r--smoketest/configs/basic-vyos18
-rw-r--r--smoketest/scripts/cli/base_vyostest_shim.py23
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py28
-rwxr-xr-xsmoketest/scripts/cli/test_high-availability_vrrp.py27
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py57
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py30
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py13
-rwxr-xr-xsmoketest/scripts/cli/test_policy_route.py11
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py4
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospfv3.py10
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py2
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py12
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py23
-rwxr-xr-xsrc/conf_mode/container.py23
-rwxr-xr-xsrc/conf_mode/firewall.py9
-rwxr-xr-xsrc/conf_mode/high-availability.py10
-rwxr-xr-xsrc/conf_mode/nat.py18
-rwxr-xr-xsrc/conf_mode/protocols_ospf.py6
-rwxr-xr-xsrc/conf_mode/protocols_ospfv3.py8
-rwxr-xr-xsrc/conf_mode/system_conntrack.py4
-rwxr-xr-xsrc/conf_mode/system_login_banner.py22
-rwxr-xr-xsrc/conf_mode/system_option.py2
-rwxr-xr-xsrc/conf_mode/vpn_ipsec.py21
-rwxr-xr-xsrc/conf_mode/vrf.py18
-rwxr-xr-xsrc/init/vyos-router4
-rwxr-xr-xsrc/migration-scripts/dhcp-server/6-to-769
-rwxr-xr-xsrc/migration-scripts/dhcp-server/7-to-866
-rwxr-xr-xsrc/migration-scripts/dhcp-server/8-to-942
-rwxr-xr-xsrc/migration-scripts/dhcp-server/9-to-1075
-rwxr-xr-xsrc/op_mode/container.py42
-rwxr-xr-xsrc/services/vyos-configd19
-rwxr-xr-xsrc/services/vyos-http-api-server3
-rw-r--r--src/shim/vyshim.c12
-rwxr-xr-xsrc/system/vyos-event-handler.py7
67 files changed, 856 insertions, 477 deletions
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index b0586e0bb..afe3dd838 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -1,6 +1,7 @@
{
"system_conntrack": {
- "conntrack_sync": ["service_conntrack-sync"]
+ "conntrack_sync": ["service_conntrack-sync"],
+ "vrf": ["vrf"]
},
"firewall": {
"conntrack": ["system_conntrack"],
@@ -30,6 +31,9 @@
"rpki": ["protocols_rpki"],
"sstp": ["vpn_sstp"]
},
+ "vpn_ipsec": {
+ "nhrp": ["protocols_nhrp"]
+ },
"vpn_l2tp": {
"ipsec": ["vpn_ipsec"]
},
diff --git a/data/templates/conntrack/nftables-ct.j2 b/data/templates/conntrack/nftables-ct.j2
index 762a6f693..c753e6bcb 100644
--- a/data/templates/conntrack/nftables-ct.j2
+++ b/data/templates/conntrack/nftables-ct.j2
@@ -40,9 +40,6 @@ table ip vyos_conntrack {
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
-{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %}
- counter jump VYOS_CT_HELPER
-{% endif %}
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump FW_CONNTRACK
@@ -51,11 +48,15 @@ table ip vyos_conntrack {
notrack
}
- chain OUTPUT {
- type filter hook output priority -300; policy accept;
{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %}
+ chain PREROUTING_HELPER {
+ type filter hook prerouting priority -5; policy accept;
counter jump VYOS_CT_HELPER
+ }
{% endif %}
+
+ chain OUTPUT {
+ type filter hook output priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump FW_CONNTRACK
@@ -66,6 +67,13 @@ table ip vyos_conntrack {
notrack
}
+{% if ipv4_firewall_action == 'accept' or ipv4_nat_action == 'accept' %}
+ chain OUTPUT_HELPER {
+ type filter hook output priority -5; policy accept;
+ counter jump VYOS_CT_HELPER
+ }
+{% endif %}
+
{{ helper_tmpl.conntrack_helpers(module_map, modules, ipv4=True) }}
chain FW_CONNTRACK {
@@ -122,9 +130,6 @@ table ip6 vyos_conntrack {
chain PREROUTING {
type filter hook prerouting priority -300; policy accept;
-{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %}
- counter jump VYOS_CT_HELPER
-{% endif %}
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump FW_CONNTRACK
@@ -132,11 +137,15 @@ table ip6 vyos_conntrack {
notrack
}
- chain OUTPUT {
- type filter hook output priority -300; policy accept;
{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %}
+ chain PREROUTING_HELPER {
+ type filter hook prerouting priority -5; policy accept;
counter jump VYOS_CT_HELPER
+ }
{% endif %}
+
+ chain OUTPUT {
+ type filter hook output priority -300; policy accept;
counter jump VYOS_CT_IGNORE
counter jump VYOS_CT_TIMEOUT
counter jump FW_CONNTRACK
@@ -144,6 +153,13 @@ table ip6 vyos_conntrack {
notrack
}
+{% if ipv6_firewall_action == 'accept' or ipv6_nat_action == 'accept' %}
+ chain OUTPUT_HELPER {
+ type filter hook output priority -5; policy accept;
+ counter jump VYOS_CT_HELPER
+ }
+{% endif %}
+
{{ helper_tmpl.conntrack_helpers(module_map, modules, ipv4=False) }}
chain FW_CONNTRACK {
diff --git a/data/templates/conntrackd/conntrackd.conf.j2 b/data/templates/conntrackd/conntrackd.conf.j2
index 8f56c8171..669b20877 100644
--- a/data/templates/conntrackd/conntrackd.conf.j2
+++ b/data/templates/conntrackd/conntrackd.conf.j2
@@ -76,7 +76,7 @@ General {
HashSize {{ hash_size }}
HashLimit {{ table_size | int *2 }}
LogFile off
- Syslog on
+ Syslog {{ 'off' if disable_syslog is vyos_defined else 'on' }}
LockFile /var/lock/conntrack.lock
UNIX {
Path /var/run/conntrackd.ctl
diff --git a/data/templates/frr/ospf6d.frr.j2 b/data/templates/frr/ospf6d.frr.j2
index b0b5663dd..5f758f9e5 100644
--- a/data/templates/frr/ospf6d.frr.j2
+++ b/data/templates/frr/ospf6d.frr.j2
@@ -109,7 +109,7 @@ router ospf6 {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
{% endif %}
{% if redistribute is vyos_defined %}
{% for protocol, options in redistribute.items() %}
- redistribute {{ protocol }} {{ 'route-map ' ~ options.route_map if options.route_map is vyos_defined }}
+ redistribute {{ protocol }} {{ 'metric ' ~ options.metric if options.metric is vyos_defined }} {{ 'metric-type ' ~ options.metric_type if options.metric_type is vyos_defined }} {{ 'route-map ' ~ options.route_map if options.route_map is vyos_defined }}
{% endfor %}
{% endif %}
exit
diff --git a/data/templates/frr/ospfd.frr.j2 b/data/templates/frr/ospfd.frr.j2
index 040628e82..ab074b6a2 100644
--- a/data/templates/frr/ospfd.frr.j2
+++ b/data/templates/frr/ospfd.frr.j2
@@ -214,13 +214,13 @@ router ospf {{ 'vrf ' ~ vrf if vrf is vyos_defined }}
passive-interface default
{% endif %}
{% if redistribute is vyos_defined %}
-{% for protocol, protocols_options in redistribute.items() %}
+{% for protocol, options in redistribute.items() %}
{% if protocol == 'table' %}
-{% for table, table_options in protocols_options.items() %}
- redistribute {{ protocol }} {{ table }} {{ 'metric ' + table_options.metric if table_options.metric is vyos_defined }} {{ 'metric-type ' + table_options.metric_type if table_options.metric_type is vyos_defined }} {{ 'route-map ' + table_options.route_map if table_options.route_map is vyos_defined }}
+{% for table, table_options in options.items() %}
+ redistribute {{ protocol }} {{ table }} {{ 'metric ' ~ table_options.metric if table_options.metric is vyos_defined }} {{ 'metric-type ' ~ table_options.metric_type if table_options.metric_type is vyos_defined }} {{ 'route-map ' ~ table_options.route_map if table_options.route_map is vyos_defined }}
{% endfor %}
{% else %}
- redistribute {{ protocol }} {{ 'metric ' + protocols_options.metric if protocols_options.metric is vyos_defined }} {{ 'metric-type ' + protocols_options.metric_type if protocols_options.metric_type is vyos_defined }} {{ 'route-map ' + protocols_options.route_map if protocols_options.route_map is vyos_defined }}
+ redistribute {{ protocol }} {{ 'metric ' ~ options.metric if options.metric is vyos_defined }} {{ 'metric-type ' ~ options.metric_type if options.metric_type is vyos_defined }} {{ 'route-map ' ~ options.route_map if options.route_map is vyos_defined }}
{% endif %}
{% endfor %}
{% endif %}
diff --git a/data/templates/high-availability/keepalived.conf.j2 b/data/templates/high-availability/keepalived.conf.j2
index d54f575b5..f34ce64e2 100644
--- a/data/templates/high-availability/keepalived.conf.j2
+++ b/data/templates/high-availability/keepalived.conf.j2
@@ -82,7 +82,11 @@ vrrp_instance {{ name }} {
nopreempt
{% endif %}
{% if group_config.peer_address is vyos_defined %}
- unicast_peer { {{ group_config.peer_address }} }
+ unicast_peer {
+{% for peer_address in group_config.peer_address %}
+ {{ peer_address }}
+{% endfor %}
+ }
{% endif %}
{% if group_config.hello_source_address is vyos_defined %}
{% if group_config.peer_address is vyos_defined %}
diff --git a/data/templates/load-balancing/wlb.conf.j2 b/data/templates/load-balancing/wlb.conf.j2
index 6557b6f4c..7f04d797e 100644
--- a/data/templates/load-balancing/wlb.conf.j2
+++ b/data/templates/load-balancing/wlb.conf.j2
@@ -93,6 +93,8 @@ rule {{ rule }} {
{% if rule_config.destination.port is vyos_defined %}
{% if '-' in rule_config.destination.port %}
port-ipt "-m multiport --dports {{ rule_config.destination.port | replace('-', ':') }}"
+{% elif ',' in rule_config.destination.port %}
+ port-ipt "-m multiport --dports {{ rule_config.destination.port }}"
{% else %}
port-ipt " --dport {{ rule_config.destination.port }}"
{% endif %}
@@ -107,6 +109,8 @@ rule {{ rule }} {
{% if rule_config.source.port is vyos_defined %}
{% if '-' in rule_config.source.port %}
port-ipt "-m multiport --sports {{ rule_config.source.port | replace('-', ':') }}"
+{% elif ',' in rule_config.destination.port %}
+ port-ipt "-m multiport --sports {{ rule_config.source.port }}"
{% else %}
port.ipt " --sport {{ rule_config.source.port }}"
{% endif %}
diff --git a/data/templates/login/default_motd.j2 b/data/templates/login/default_motd.j2
new file mode 100644
index 000000000..543c6f8e0
--- /dev/null
+++ b/data/templates/login/default_motd.j2
@@ -0,0 +1,14 @@
+Welcome to VyOS!
+
+ ┌── ┐
+ . VyOS {{ version_data.version }}
+ └ ──┘ {{ version_data.release_train }}
+
+ * Documentation: {{ version_data.documentation_url }}
+ * Project news: {{ version_data.project_news_url }}
+ * Bug reports: {{ version_data.bugtracker_url }}
+
+You can change this banner using "set system login banner post-login" command.
+
+VyOS is a free software distribution that includes multiple components,
+you can check individual component licenses under /usr/share/doc/*/copyright
diff --git a/data/templates/vyos-hostsd/hosts.j2 b/data/templates/vyos-hostsd/hosts.j2
index 5cad983b4..62ecf3ad0 100644
--- a/data/templates/vyos-hostsd/hosts.j2
+++ b/data/templates/vyos-hostsd/hosts.j2
@@ -4,7 +4,7 @@
# Local host
127.0.0.1 localhost
-127.0.1.1 {{ host_name }}{% if domain_name %}.{{ domain_name }} {{ host_name }}{% endif %}
+127.0.1.1 {{ host_name }}
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
diff --git a/data/vyos-firewall-init.conf b/data/vyos-firewall-init.conf
index 5a4e03015..3929edf0b 100644
--- a/data/vyos-firewall-init.conf
+++ b/data/vyos-firewall-init.conf
@@ -65,11 +65,9 @@ table inet vrf_zones {
# Chain for inbound traffic
chain vrf_zones_ct_in {
type filter hook prerouting priority raw; policy accept;
- counter ct original zone set iifname map @ct_iface_map
}
# Chain for locally-generated traffic
chain vrf_zones_ct_out {
type filter hook output priority raw; policy accept;
- counter ct original zone set oifname map @ct_iface_map
}
}
diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in
index 59f0f1052..aef57f8ae 100644
--- a/interface-definitions/high-availability.xml.in
+++ b/interface-definitions/high-availability.xml.in
@@ -195,6 +195,7 @@
<constraint>
<validator name="ip-address"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
<leafNode name="no-preempt">
diff --git a/interface-definitions/include/accel-ppp/extended-scripts.xml.i b/interface-definitions/include/accel-ppp/extended-scripts.xml.i
index 4bba76e32..53ff6d591 100644
--- a/interface-definitions/include/accel-ppp/extended-scripts.xml.i
+++ b/interface-definitions/include/accel-ppp/extended-scripts.xml.i
@@ -6,7 +6,7 @@
<children>
<leafNode name="on-pre-up">
<properties>
- <help>Script to run before PPPoE session interface comes up</help>
+ <help>Script to run before session interface comes up</help>
<constraint>
<validator name="script"/>
</constraint>
@@ -14,7 +14,7 @@
</leafNode>
<leafNode name="on-up">
<properties>
- <help>Script to run when PPPoE session interface is completely configured and started</help>
+ <help>Script to run when session interface is completely configured and started</help>
<constraint>
<validator name="script"/>
</constraint>
@@ -22,7 +22,7 @@
</leafNode>
<leafNode name="on-down">
<properties>
- <help>Script to run when PPPoE session interface going to terminate</help>
+ <help>Script to run when session interface going to terminate</help>
<constraint>
<validator name="script"/>
</constraint>
@@ -30,7 +30,7 @@
</leafNode>
<leafNode name="on-change">
<properties>
- <help>Script to run when PPPoE session interface changed by RADIUS CoA handling</help>
+ <help>Script to run when session interface changed by RADIUS CoA handling</help>
<constraint>
<validator name="script"/>
</constraint>
diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i
index 6de770c79..dcdd970ac 100644
--- a/interface-definitions/include/firewall/common-rule-bridge.xml.i
+++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i
@@ -9,12 +9,7 @@
#include <include/firewall/mac-address.xml.i>
</children>
</node>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
+#include <include/generic-disable-node.xml.i>
<leafNode name="jump-target">
<properties>
<help>Set jump target. Action jump must be defined to use this setting</help>
diff --git a/interface-definitions/include/firewall/common-rule-inet.xml.i b/interface-definitions/include/firewall/common-rule-inet.xml.i
index 85189d975..bef1c3da5 100644
--- a/interface-definitions/include/firewall/common-rule-inet.xml.i
+++ b/interface-definitions/include/firewall/common-rule-inet.xml.i
@@ -7,12 +7,7 @@
#include <include/firewall/connection-mark.xml.i>
#include <include/firewall/conntrack-helper.xml.i>
#include <include/firewall/nft-queue.xml.i>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
+#include <include/generic-disable-node.xml.i>
<node name="fragment">
<properties>
<help>IP fragment match</help>
diff --git a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
index 0d749aa27..e7468bfba 100644
--- a/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
+++ b/interface-definitions/include/firewall/common-rule-ipv4-raw.xml.i
@@ -18,12 +18,7 @@
#include <include/firewall/source-destination-group.xml.i>
</children>
</node>
-<leafNode name="disable">
- <properties>
- <help>Option to disable firewall rule</help>
- <valueless/>
- </properties>
-</leafNode>
+#include <include/generic-disable-node.xml.i>
<node name="fragment">
<properties>
<help>IP fragment match</help>
diff --git a/interface-definitions/include/ospfv3/protocol-common-config.xml.i b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
index 4c3ca68e1..72fb86d3d 100644
--- a/interface-definitions/include/ospfv3/protocol-common-config.xml.i
+++ b/interface-definitions/include/ospfv3/protocol-common-config.xml.i
@@ -221,11 +221,23 @@
<help>Redistribute information from another routing protocol</help>
</properties>
<children>
+ <node name="babel">
+ <properties>
+ <help>Redistribute Babel routes</help>
+ </properties>
+ <children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
+ #include <include/route-map.xml.i>
+ </children>
+ </node>
<node name="bgp">
<properties>
<help>Redistribute BGP routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
@@ -234,30 +246,38 @@
<help>Redistribute connected routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
- <node name="kernel">
+ <node name="isis">
<properties>
- <help>Redistribute kernel routes</help>
+ <help>Redistribute IS-IS routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
- <node name="ripng">
+ <node name="kernel">
<properties>
- <help>Redistribute RIPNG routes</help>
+ <help>Redistribute kernel routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
- <node name="babel">
+ <node name="ripng">
<properties>
- <help>Redistribute Babel routes</help>
+ <help>Redistribute RIPNG routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
@@ -266,6 +286,8 @@
<help>Redistribute static routes</help>
</properties>
<children>
+ #include <include/ospf/metric.xml.i>
+ #include <include/ospf/metric-type.xml.i>
#include <include/route-map.xml.i>
</children>
</node>
diff --git a/interface-definitions/include/pki/cli-private-key-base64.xml.i b/interface-definitions/include/pki/cli-private-key-base64.xml.i
index 3a7ee0ce9..f57e9b198 100644
--- a/interface-definitions/include/pki/cli-private-key-base64.xml.i
+++ b/interface-definitions/include/pki/cli-private-key-base64.xml.i
@@ -1,4 +1,4 @@
-<!-- include start from pki/pki-cli-private-key.xml.i -->
+<!-- include start from pki/cli-private-key-base64.xml.i -->
<leafNode name="key">
<properties>
<help>Private key in PEM format</help>
diff --git a/interface-definitions/include/pki/cli-public-key-base64.xml.i b/interface-definitions/include/pki/cli-public-key-base64.xml.i
index 3a7ee0ce9..f7cffae55 100644
--- a/interface-definitions/include/pki/cli-public-key-base64.xml.i
+++ b/interface-definitions/include/pki/cli-public-key-base64.xml.i
@@ -1,11 +1,11 @@
-<!-- include start from pki/pki-cli-private-key.xml.i -->
+<!-- include start from pki/cli-public-key-base64.xml.i -->
<leafNode name="key">
<properties>
- <help>Private key in PEM format</help>
+ <help>Public key in PEM format</help>
<constraint>
<validator name="base64"/>
</constraint>
- <constraintErrorMessage>Private key is not base64-encoded</constraintErrorMessage>
+ <constraintErrorMessage>Public key is not base64-encoded</constraintErrorMessage>
</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 d83172e72..3dcbc513a 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='9'></syntaxVersion>
+<syntaxVersion component='dhcp-server' version='10'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in
index b5da0a556..458f7ebb3 100644
--- a/interface-definitions/interfaces_wireless.xml.in
+++ b/interface-definitions/interfaces_wireless.xml.in
@@ -455,14 +455,18 @@
<properties>
<help>Indicate country in which device is operating</help>
<completionHelp>
- <list>us eu jp de uk cn es fr ru</list>
+ <list>00 ad ae af ai al am an ar as at au aw az ba bb bd be bf bg bh bl bm bn bo br bs bt by bz ca cf ch ci cl cn co cr cu cx cy cz de dk dm do dz ec ee eg es et fi fm fr gb gd ge gf gh gl gp gr gt gu gy hk hn hr ht hu id ie il in ir is it jm jo jp ke kh kn kp kr kw ky kz lb lc li lk ls lt lu lv ma mc md me mf mh mk mn mo mp mq mr mt mu mv mw mx my ng ni nl no np nz om pa pe pf pg ph pk pl pm pr pt pw py qa re ro rs ru rw sa se sg si sk sn sr sv sy tc td tg th tn tr tt tw tz ua ug us uy uz vc ve vi vn vu wf ws ye yt za zw</list>
</completionHelp>
<valueHelp>
+ <format>00</format>
+ <description>World regulatory domain</description>
+ </valueHelp>
+ <valueHelp>
<format>txt</format>
<description>ISO/IEC 3166-1 Country Code</description>
</valueHelp>
<constraint>
- <regex>[a-z][a-z]</regex>
+ <regex>(00|ad|ae|af|ai|al|am|an|ar|as|at|au|aw|az|ba|bb|bd|be|bf|bg|bh|bl|bm|bn|bo|br|bs|bt|by|bz|ca|cf|ch|ci|cl|cn|co|cr|cu|cx|cy|cz|de|dk|dm|do|dz|ec|ee|eg|es|et|fi|fm|fr|gb|gd|ge|gf|gh|gl|gp|gr|gt|gu|gy|hk|hn|hr|ht|hu|id|ie|il|in|ir|is|it|jm|jo|jp|ke|kh|kn|kp|kr|kw|ky|kz|lb|lc|li|lk|ls|lt|lu|lv|ma|mc|md|me|mf|mh|mk|mn|mo|mp|mq|mr|mt|mu|mv|mw|mx|my|ng|ni|nl|no|np|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pr|pt|pw|py|qa|re|ro|rs|ru|rw|sa|se|sg|si|sk|sn|sr|sv|sy|tc|td|tg|th|tn|tr|tt|tw|tz|ua|ug|us|uy|uz|vc|ve|vi|vn|vu|wf|ws|ye|yt|za|zw)</regex>
</constraint>
<constraintErrorMessage>Invalid ISO/IEC 3166-1 Country Code</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/service_conntrack-sync.xml.in b/interface-definitions/service_conntrack-sync.xml.in
index 46dc8adc0..397864867 100644
--- a/interface-definitions/service_conntrack-sync.xml.in
+++ b/interface-definitions/service_conntrack-sync.xml.in
@@ -52,6 +52,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="disable-syslog">
+ <properties>
+ <help>Disable connection logging via Syslog</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="event-listen-queue-size">
<properties>
<help>Queue size for local conntrack events</help>
diff --git a/interface-definitions/service_dhcp-server.xml.in b/interface-definitions/service_dhcp-server.xml.in
index 5c9d4a360..1c10a462d 100644
--- a/interface-definitions/service_dhcp-server.xml.in
+++ b/interface-definitions/service_dhcp-server.xml.in
@@ -122,6 +122,12 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="ignore-client-id">
+ <properties>
+ <help>Ignore client identifier for lease lookups</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="lease">
<properties>
<help>Lease timeout in seconds</help>
diff --git a/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in
index 602d7d100..fe517d17d 100644
--- a/interface-definitions/system_option.xml.in
+++ b/interface-definitions/system_option.xml.in
@@ -43,6 +43,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="disable-power-saving">
+ <properties>
+ <help>Disable CPU power saving mechanisms also known as C states</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</node>
<leafNode name="keyboard-layout">
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index 96c582a83..4aa13e913 100644
--- a/op-mode-definitions/container.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -41,6 +41,7 @@
<properties>
<help>Delete container image</help>
<completionHelp>
+ <list>all</list>
<script>sudo podman image ls -q</script>
</completionHelp>
</properties>
diff --git a/op-mode-definitions/pki.xml.in b/op-mode-definitions/pki.xml.in
index 4b8d9c47a..a81c8d4f7 100644
--- a/op-mode-definitions/pki.xml.in
+++ b/op-mode-definitions/pki.xml.in
@@ -27,7 +27,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -36,10 +36,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$7" --sign "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "noname" --sign "$5"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname" --sign "$5"</command>
</tagNode>
<tagNode name="file">
<properties>
@@ -48,7 +48,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -57,10 +57,10 @@
<list>&lt;CA name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ca "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ca "noname"</command>
</node>
<node name="certificate">
<properties>
@@ -79,7 +79,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -88,10 +88,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$6" --self-sign --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --self-sign</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --self-sign</command>
</node>
<tagNode name="sign">
<properties>
@@ -108,7 +108,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -117,10 +117,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$7" --sign "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --sign "$5"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname" --sign "$5"</command>
</tagNode>
<tagNode name="file">
<properties>
@@ -129,7 +129,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -138,10 +138,10 @@
<list>&lt;certificate name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --certificate "noname"</command>
</node>
<tagNode name="crl">
<properties>
@@ -158,16 +158,16 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --file</command>
</tagNode>
<leafNode name="install">
<properties>
<help>Commands for installing generated CRL into running configuration</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4" --install</command>
</leafNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --crl "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --crl "$4"</command>
</tagNode>
<node name="dh">
<properties>
@@ -181,7 +181,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -190,10 +190,10 @@
<list>&lt;DH name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --dh "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --dh "noname"</command>
</node>
<node name="key-pair">
<properties>
@@ -207,7 +207,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -216,10 +216,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --keypair "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --keypair "noname"</command>
</node>
<node name="openvpn">
<properties>
@@ -238,7 +238,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -247,10 +247,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "$6" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --openvpn "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --openvpn "noname"</command>
</node>
</children>
</node>
@@ -266,7 +266,7 @@
<list>&lt;filename&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --file</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --file</command>
</tagNode>
<tagNode name="install">
<properties>
@@ -275,10 +275,10 @@
<list>&lt;key name&gt;</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "$5" --install</command>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --ssh "noname"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --ssh "noname"</command>
</node>
<node name="wireguard">
<properties>
@@ -302,12 +302,12 @@
<path>interfaces wireguard</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key --interface "$7" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key --interface "$7" --install</command>
</tagNode>
</children>
</node>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --key</command>
</node>
<node name="preshared-key">
<properties>
@@ -334,14 +334,14 @@
<path>interfaces wireguard ${COMP_WORDS[COMP_CWORD-2]} peer</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk --interface "$7" --peer "$9" --install</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk --interface "$7" --peer "$9" --install</command>
</tagNode>
</children>
</tagNode>
</children>
</node>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action generate --wireguard --psk</command>
</node>
</children>
</node>
@@ -371,13 +371,13 @@
<properties>
<help>Path to CA certificate file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --filename "$6"</command>
</tagNode>
<tagNode name="key-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --ca "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -393,13 +393,13 @@
<properties>
<help>Path to certificate file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --filename "$6"</command>
</tagNode>
<tagNode name="key-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --certificate "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -415,7 +415,7 @@
<properties>
<help>Path to CRL file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --crl "$4" --filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -431,7 +431,7 @@
<properties>
<help>Path to DH parameters file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --dh "$4" --filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -447,13 +447,13 @@
<properties>
<help>Path to public key file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --filename "$6"</command>
</tagNode>
<tagNode name="private-file">
<properties>
<help>Path to private key file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --keypair "$4" --key-filename "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -474,7 +474,7 @@
<properties>
<help>Path to shared secret key file</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command>
+ <command>sudo -E ${vyos_op_scripts_dir}/pki.py --action import --openvpn "$5" --filename "$7"</command>
</tagNode>
</children>
</tagNode>
@@ -495,7 +495,7 @@
<properties>
<help>Show x509 CA certificates</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "all"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --ca "all"</command>
</leafNode>
<tagNode name="ca">
<properties>
@@ -504,13 +504,13 @@
<path>pki ca</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --ca "$4"</command>
<children>
<leafNode name="pem">
<properties>
<help>Show x509 CA certificate in PEM format</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --ca "$4" --pem</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --ca "$4" --pem</command>
</leafNode>
</children>
</tagNode>
@@ -518,7 +518,7 @@
<properties>
<help>Show x509 certificates</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "all"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --certificate "all"</command>
</leafNode>
<tagNode name="certificate">
<properties>
@@ -527,7 +527,7 @@
<path>pki certificate</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --certificate "$4"</command>
<children>
<leafNode name="pem">
<properties>
@@ -542,7 +542,7 @@
<list>sha256 sha384 sha512</list>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --fingerprint "$6"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --certificate "$4" --fingerprint "$6"</command>
</tagNode>
</children>
</tagNode>
@@ -550,7 +550,7 @@
<properties>
<help>Show x509 certificate revocation lists</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --crl "all"</command>
</leafNode>
<tagNode name="crl">
<properties>
@@ -559,18 +559,18 @@
<path>pki ca</path>
</completionHelp>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "$4"</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4"</command>
<children>
<leafNode name="pem">
<properties>
<help>Show x509 certificate revocation lists by CA name in PEM format</help>
</properties>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show --crl "$4" --pem</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show --crl "$4" --pem</command>
</leafNode>
</children>
</tagNode>
</children>
- <command>sudo ${vyos_op_scripts_dir}/pki.py --action show</command>
+ <command>${vyos_op_scripts_dir}/pki.py --action show</command>
</node>
</children>
</node>
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 64727d355..73bd9ea96 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-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
@@ -33,7 +33,14 @@ if typing.TYPE_CHECKING:
dependency_dir = os.path.join(directories['data'],
'config-mode-dependencies')
-dependent_func: dict[str, list[typing.Callable]] = {}
+local_dependent_func: dict[str, list[typing.Callable]] = {}
+
+DEBUG = False
+FORCE_LOCAL = False
+
+def debug_print(s: str):
+ if DEBUG:
+ print(s)
def canon_name(name: str) -> str:
return os.path.splitext(name)[0].replace('-', '_')
@@ -45,6 +52,26 @@ def canon_name_of_path(path: str) -> str:
def caller_name() -> str:
return stack()[2].filename
+def name_of(f: typing.Callable) -> str:
+ return f.__name__
+
+def names_of(l: list[typing.Callable]) -> list[str]:
+ return [name_of(f) for f in l]
+
+def remove_redundant(l: list[typing.Callable]) -> list[typing.Callable]:
+ names = set()
+ for e in reversed(l):
+ _ = l.remove(e) if name_of(e) in names else names.add(name_of(e))
+
+def append_uniq(l: list[typing.Callable], e: typing.Callable):
+ """Append an element, removing earlier occurrences
+
+ The list of dependencies is generally short and traversing the list on
+ each append is preferable to the cost of redundant script invocation.
+ """
+ l.append(e)
+ remove_redundant(l)
+
def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict:
res = {}
for dep_file in os.listdir(dependency_dir):
@@ -95,16 +122,30 @@ def set_dependents(case: str, config: 'Config',
tagnode: typing.Optional[str] = None):
d = get_dependency_dict(config)
k = canon_name_of_path(caller_name())
- l = dependent_func.setdefault(k, [])
+ tag_ext = f'_{tagnode}' if tagnode is not None else ''
+ if hasattr(config, 'dependent_func') and not FORCE_LOCAL:
+ dependent_func = getattr(config, 'dependent_func')
+ l = dependent_func.setdefault('vyos_configd', [])
+ else:
+ dependent_func = local_dependent_func
+ l = dependent_func.setdefault(k, [])
for target in d[k][case]:
func = def_closure(target, config, tagnode)
- l.append(func)
+ func.__name__ = f'{target}{tag_ext}'
+ append_uniq(l, func)
+ debug_print(f'set_dependents: caller {k}, dependents {names_of(l)}')
-def call_dependents():
+def call_dependents(dependent_func: dict = None):
k = canon_name_of_path(caller_name())
- l = dependent_func.get(k, [])
+ if dependent_func is None or FORCE_LOCAL:
+ dependent_func = local_dependent_func
+ l = dependent_func.get(k, [])
+ else:
+ l = dependent_func.get('vyos_configd', [])
+ debug_print(f'call_dependents: caller {k}, dependents {names_of(l)}')
while l:
f = l.pop(0)
+ debug_print(f'calling: {f.__name__}')
f()
def called_as_dependent() -> bool:
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index ba638b280..473c98d0c 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -1,4 +1,4 @@
-# Copyright 2021-2023 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
@@ -16,6 +16,7 @@
import os
import re
+from json import loads
from vyos.utils.process import popen
# These drivers do not support using ethtool to change the speed, duplex, or
@@ -31,16 +32,24 @@ class Ethtool:
"""
# dictionary containing driver featurs, it will be populated on demand and
# the content will look like:
- # {
- # 'tls-hw-tx-offload': {'fixed': True, 'enabled': False},
- # 'tx-checksum-fcoe-crc': {'fixed': True, 'enabled': False},
- # 'tx-checksum-ip-generic': {'fixed': False, 'enabled': True},
- # 'tx-checksum-ipv4': {'fixed': True, 'enabled': False},
- # 'tx-checksum-ipv6': {'fixed': True, 'enabled': False},
- # 'tx-checksum-sctp': {'fixed': True, 'enabled': False},
- # 'tx-checksumming': {'fixed': False, 'enabled': True},
- # 'tx-esp-segmentation': {'fixed': True, 'enabled': False},
- # }
+ # [{'esp-hw-offload': {'active': False, 'fixed': True, 'requested': False},
+ # 'esp-tx-csum-hw-offload': {'active': False,
+ # 'fixed': True,
+ # 'requested': False},
+ # 'fcoe-mtu': {'active': False, 'fixed': True, 'requested': False},
+ # 'generic-receive-offload': {'active': True,
+ # 'fixed': False,
+ # 'requested': True},
+ # 'generic-segmentation-offload': {'active': True,
+ # 'fixed': False,
+ # 'requested': True},
+ # 'highdma': {'active': True, 'fixed': False, 'requested': True},
+ # 'ifname': 'eth0',
+ # 'l2-fwd-offload': {'active': False, 'fixed': True, 'requested': False},
+ # 'large-receive-offload': {'active': False,
+ # 'fixed': False,
+ # 'requested': False},
+ # ...
_features = { }
# dictionary containing available interface speed and duplex settings
# {
@@ -49,13 +58,11 @@ class Ethtool:
# '1000': {'full': ''}
# }
_speed_duplex = {'auto': {'auto': ''}}
- _ring_buffers = { }
- _ring_buffers_max = { }
+ _ring_buffer = None
_driver_name = None
_auto_negotiation = False
_auto_negotiation_supported = None
- _flow_control = False
- _flow_control_enabled = None
+ _flow_control = None
_eee = False
_eee_enabled = None
@@ -97,51 +104,19 @@ class Ethtool:
tmp = line.split()[-1]
self._auto_negotiation = bool(tmp == 'on')
- # Now populate features dictionaty
- out, _ = popen(f'ethtool --show-features {ifname}')
- # skip the first line, it only says: "Features for eth0":
- for line in out.splitlines()[1:]:
- if ":" in line:
- key, value = [s.strip() for s in line.strip().split(":", 1)]
- fixed = bool('fixed' in value)
- if fixed:
- value = value.split()[0].strip()
- self._features[key.strip()] = {
- 'enabled' : bool(value == 'on'),
- 'fixed' : fixed
- }
-
- out, _ = popen(f'ethtool --show-ring {ifname}')
- # We are only interested in line 2-5 which contains the device maximum
- # ringbuffers
- for line in out.splitlines()[2:6]:
- if ':' in line:
- key, value = [s.strip() for s in line.strip().split(":", 1)]
- key = key.lower().replace(' ', '_')
- # T3645: ethtool version used on Debian Bullseye changed the
- # output format from 0 -> n/a. As we are only interested in the
- # tx/rx keys we do not care about RX Mini/Jumbo.
- if value.isdigit():
- self._ring_buffers_max[key] = value
- # Now we wan't to get the current RX/TX ringbuffer values - used for
- for line in out.splitlines()[7:11]:
- if ':' in line:
- key, value = [s.strip() for s in line.strip().split(":", 1)]
- key = key.lower().replace(' ', '_')
- # T3645: ethtool version used on Debian Bullseye changed the
- # output format from 0 -> n/a. As we are only interested in the
- # tx/rx keys we do not care about RX Mini/Jumbo.
- if value.isdigit():
- self._ring_buffers[key] = value
+ # Now populate driver features
+ out, _ = popen(f'ethtool --json --show-features {ifname}')
+ self._features = loads(out)
+
+ # Get information about NIC ring buffers
+ out, _ = popen(f'ethtool --json --show-ring {ifname}')
+ self._ring_buffer = loads(out)
# Get current flow control settings, but this is not supported by
# all NICs (e.g. vmxnet3 does not support is)
- out, _ = popen(f'ethtool --show-pause {ifname}')
- if len(out.splitlines()) > 1:
- self._flow_control = True
- # read current flow control setting, this returns:
- # ['Autonegotiate:', 'on']
- self._flow_control_enabled = out.splitlines()[1].split()[-1]
+ out, err = popen(f'ethtool --json --show-pause {ifname}')
+ 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)
@@ -150,7 +125,7 @@ class Ethtool:
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()[2])
+ self._eee_enabled = bool('enabled' in out.splitlines()[1])
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
@@ -169,14 +144,12 @@ class Ethtool:
In case of a missing key, return "fixed = True and enabled = False"
"""
+ active = False
fixed = True
- enabled = False
- if feature in self._features:
- if 'enabled' in self._features[feature]:
- enabled = self._features[feature]['enabled']
- if 'fixed' in self._features[feature]:
- fixed = self._features[feature]['fixed']
- return enabled, fixed
+ if feature in self._features[0]:
+ active = bool(self._features[0][feature]['active'])
+ fixed = bool(self._features[0][feature]['fixed'])
+ return active, fixed
def get_generic_receive_offload(self):
return self._get_generic('generic-receive-offload')
@@ -201,14 +174,14 @@ class Ethtool:
# thus when it's impossible return None
if rx_tx not in ['rx', 'tx']:
ValueError('Ring-buffer type must be either "rx" or "tx"')
- return self._ring_buffers_max.get(rx_tx, None)
+ return str(self._ring_buffer[0].get(f'{rx_tx}-max', None))
def get_ring_buffer(self, rx_tx):
# Configuration of RX/TX ring-buffers is not supported on every device,
# thus when it's impossible return None
if rx_tx not in ['rx', 'tx']:
ValueError('Ring-buffer type must be either "rx" or "tx"')
- return str(self._ring_buffers.get(rx_tx, None))
+ return str(self._ring_buffer[0].get(rx_tx, None))
def check_speed_duplex(self, speed, duplex):
""" Check if the passed speed and duplex combination is supported by
@@ -230,15 +203,14 @@ class Ethtool:
def check_flow_control(self):
""" Check if the NIC supports flow-control """
- if self.get_driver_name() in _drivers_without_speed_duplex_flow:
- return False
- return self._flow_control
+ return bool(self._flow_control)
def get_flow_control(self):
- if self._flow_control_enabled == None:
+ if self._flow_control == None:
raise ValueError('Interface does not support changing '\
'flow-control settings!')
- return self._flow_control_enabled
+
+ return 'on' if bool(self._flow_control[0]['autonegotiate']) else 'off'
def check_eee(self):
""" Check if the NIC supports eee """
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index eee11bd2d..e70b4f0d9 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -34,6 +34,24 @@ from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
+# Conntrack
+
+def conntrack_required(conf):
+ required_nodes = ['nat', 'nat66', 'load-balancing wan']
+
+ for path in required_nodes:
+ if conf.exists(path):
+ return True
+
+ firewall = conf.get_config_dict(['firewall'], key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True, get_first_key=True)
+
+ for rules, path in dict_search_recursive(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()):
+ return True
+
+ return False
+
# Domain Resolver
def fqdn_config_parse(firewall):
@@ -118,10 +136,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if 'connection_status' in rule_conf and rule_conf['connection_status']:
status = rule_conf['connection_status']
if status['nat'] == 'destination':
- nat_status = '{dnat}'
+ nat_status = 'dnat'
output.append(f'ct status {nat_status}')
if status['nat'] == 'source':
- nat_status = '{snat}'
+ nat_status = 'snat'
output.append(f'ct status {nat_status}')
if 'protocol' in rule_conf and rule_conf['protocol'] != 'all':
diff --git a/python/vyos/kea.py b/python/vyos/kea.py
index 894ac9e9a..2328d0b00 100644
--- a/python/vyos/kea.py
+++ b/python/vyos/kea.py
@@ -113,6 +113,9 @@ def kea_parse_subnet(subnet, config):
if 'bootfile_server' in config['option']:
out['next-server'] = config['option']['bootfile_server']
+ if 'ignore_client_id' in config:
+ out['match-client-id'] = False
+
if 'lease' in config:
out['valid-lifetime'] = int(config['lease'])
out['max-valid-lifetime'] = int(config['lease'])
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 7215aac88..da2613b16 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -89,11 +89,14 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
if addr and is_ip_network(addr):
if not ipv6:
map_addr = dict_search_args(rule_conf, nat_type, 'address')
- if port:
- translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} . {port} }}')
+ if map_addr:
+ if port:
+ translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} . {port} }}')
+ else:
+ translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
+ ignore_type_addr = True
else:
- translation_output.append(f'{ip_prefix} prefix to {ip_prefix} {translation_prefix}addr map {{ {map_addr} : {addr} }}')
- ignore_type_addr = True
+ translation_output.append(f'prefix to {addr}')
else:
translation_output.append(f'prefix to {addr}')
elif addr == 'masquerade':
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index a22039e52..47318122b 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -129,16 +129,13 @@ class QoSBase:
if tmp: default_tc += f' flows {tmp}'
tmp = dict_search('interval', config)
- if tmp: default_tc += f' interval {tmp}'
-
- tmp = dict_search('interval', config)
- if tmp: default_tc += f' interval {tmp}'
+ if tmp: default_tc += f' interval {tmp}ms'
tmp = dict_search('queue_limit', config)
if tmp: default_tc += f' limit {tmp}'
tmp = dict_search('target', config)
- if tmp: default_tc += f' target {tmp}'
+ if tmp: default_tc += f' target {tmp}ms'
default_tc += f' noecn'
@@ -331,15 +328,6 @@ class QoSBase:
filter_cmd += f' flowid {self._parent:x}:{cls:x}'
self._cmd(filter_cmd)
- else:
-
- filter_cmd += ' basic'
-
- cls = int(cls)
- filter_cmd += f' flowid {self._parent:x}:{cls:x}'
- self._cmd(filter_cmd)
-
-
# The police block allows limiting of the byte or packet rate of
# traffic matched by the filter it is attached to.
# https://man7.org/linux/man-pages/man8/tc-police.8.html
diff --git a/python/vyos/remote.py b/python/vyos/remote.py
index 129e65772..d87fd24f6 100644
--- a/python/vyos/remote.py
+++ b/python/vyos/remote.py
@@ -43,6 +43,7 @@ from vyos.utils.io import print_error
from vyos.utils.misc import begin
from vyos.utils.process import cmd, rc_cmd
from vyos.version import get_version
+from vyos.base import Warning
CHUNK_SIZE = 8192
@@ -54,13 +55,16 @@ class InteractivePolicy(MissingHostKeyPolicy):
def missing_host_key(self, client, hostname, key):
print_error(f"Host '{hostname}' not found in known hosts.")
print_error('Fingerprint: ' + key.get_fingerprint().hex())
- if sys.stdin.isatty() and ask_yes_no('Do you wish to continue?'):
- if client._host_keys_filename\
- and ask_yes_no('Do you wish to permanently add this host/key pair to known hosts?'):
- client._host_keys.add(hostname, key.get_name(), key)
- client.save_host_keys(client._host_keys_filename)
- else:
+ if not sys.stdin.isatty():
+ return
+ if not ask_yes_no('Do you wish to continue?'):
raise SSHException(f"Cannot connect to unknown host '{hostname}'.")
+ if client._host_keys_filename is None:
+ Warning('no \'known_hosts\' file; create to store keys permanently')
+ return
+ if ask_yes_no('Do you wish to permanently add this host/key pair to known_hosts file?'):
+ client._host_keys.add(hostname, key.get_name(), key)
+ client.save_host_keys(client._host_keys_filename)
class SourceAdapter(HTTPAdapter):
"""
diff --git a/smoketest/config-tests/basic-vyos b/smoketest/config-tests/basic-vyos
index 0bb68b75d..d676c663d 100644
--- a/smoketest/config-tests/basic-vyos
+++ b/smoketest/config-tests/basic-vyos
@@ -1,13 +1,9 @@
set interfaces ethernet eth0 address '192.168.0.1/24'
-set interfaces ethernet eth0 duplex 'auto'
-set interfaces ethernet eth0 speed 'auto'
-set interfaces ethernet eth1 duplex 'auto'
-set interfaces ethernet eth1 speed 'auto'
-set interfaces ethernet eth2 duplex 'auto'
-set interfaces ethernet eth2 speed 'auto'
+set interfaces ethernet eth0 address 'fe88::1/56'
set interfaces ethernet eth2 vif 100 address '100.100.0.1/24'
set interfaces ethernet eth2 vif-s 200 address '100.64.200.254/24'
set interfaces ethernet eth2 vif-s 200 vif-c 201 address '100.64.201.254/24'
+set interfaces ethernet eth2 vif-s 200 vif-c 201 address 'fe89::1/56'
set interfaces ethernet eth2 vif-s 200 vif-c 202 address '100.64.202.254/24'
set interfaces loopback lo
set protocols static arp interface eth0 address 192.168.0.20 mac '00:50:00:00:00:20'
@@ -23,18 +19,6 @@ set protocols static arp interface eth2.200.201 address 100.64.201.20 mac '00:50
set protocols static arp interface eth2.200.202 address 100.64.202.30 mac '00:50:00:00:00:30'
set protocols static arp interface eth2.200.202 address 100.64.202.40 mac '00:50:00:00:00:40'
set protocols static route 0.0.0.0/0 next-hop 100.64.0.1
-set service dhcp-server shared-network-name LAN authoritative
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.20'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
-set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1'
-set service dns forwarding allow-from '192.168.0.0/16'
-set service dns forwarding cache-size '10000'
-set service dns forwarding dnssec 'off'
-set service dns forwarding listen-address '192.168.0.1'
set service ssh ciphers 'aes128-ctr'
set service ssh ciphers 'aes192-ctr'
set service ssh ciphers 'aes256-ctr'
@@ -46,18 +30,55 @@ set service ssh key-exchange 'diffie-hellman-group-exchange-sha1'
set service ssh key-exchange 'diffie-hellman-group-exchange-sha256'
set service ssh listen-address '192.168.0.1'
set service ssh port '22'
+set service dhcp-server shared-network-name LAN authoritative
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option default-router '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option domain-search 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 option name-server '192.168.0.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic start '192.168.0.30'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 range LANDynamic stop '192.168.0.240'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-1 ip-address '192.168.0.11'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-1 mac '00:01:02:03:04:05'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 disable
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 ip-address '192.168.0.12'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST1-2 mac '00:01:02:03:04:05'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-1 ip-address '192.168.0.21'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-1 mac '00:01:02:03:04:21'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 disable
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 ip-address '192.168.0.21'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 static-mapping TEST2-2 mac '00:01:02:03:04:22'
+set service dhcp-server shared-network-name LAN subnet 192.168.0.0/24 subnet-id '1'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 interface 'eth0'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option domain-search 'vyos.net'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 option name-server 'fe88::1'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 1 prefix 'fe88::/60'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 2 start 'fe88:0000:0000:fe::'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 range 2 stop 'fe88:0000:0000:ff::'
+set service dhcpv6-server shared-network-name LAN6 subnet fe88::/56 subnet-id '1'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 interface 'eth2.200.201'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 option domain-search 'vyos.net'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 option name-server 'fe89::1'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 1 prefix 'fe89::/60'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 2 start 'fe89:0000:0000:fe::'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 range 2 stop 'fe89:0000:0000:ff::'
+set service dhcpv6-server shared-network-name LAN6 subnet fe89::/56 subnet-id '2'
+set service dns forwarding allow-from '192.168.0.0/16'
+set service dns forwarding cache-size '10000'
+set service dns forwarding dnssec 'off'
+set service dns forwarding listen-address '192.168.0.1'
set system config-management commit-revisions '100'
-set system console device ttyS0 speed '115200'
+set system conntrack ignore ipv4 rule 1 destination address '192.0.2.2'
+set system conntrack ignore ipv4 rule 1 source address '192.0.2.1'
set system host-name 'vyos'
+set system login user vyos authentication encrypted-password '$6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0'
+set system login user vyos authentication plaintext-password ''
set system name-server '192.168.0.1'
-set system syslog console facility all level 'emerg'
-set system syslog console facility mail level 'info'
-set system syslog global facility all level 'info'
set system syslog global facility auth level 'info'
-set system syslog global facility local7 level 'debug'
set system syslog global preserve-fqdn
+set system syslog console facility all level 'emerg'
+set system syslog console facility mail level 'info'
set system syslog host syslog.vyos.net facility auth level 'warning'
set system syslog host syslog.vyos.net facility local7 level 'notice'
set system syslog host syslog.vyos.net format octet-counted
set system syslog host syslog.vyos.net port '8000'
-set system time-zone 'Europe/Berlin'
+set system console device ttyS0 speed '115200'
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 76aa52039..e95d7458f 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -86,9 +86,25 @@ service {
domain-name vyos.net
domain-search vyos.net
range LANDynamic {
- start 192.168.0.20
+ start 192.168.0.30
stop 192.168.0.240
}
+ static-mapping TEST1-1 {
+ ip-address 192.168.0.11
+ mac-address 00:01:02:03:04:05
+ }
+ static-mapping TEST1-2 {
+ ip-address 192.168.0.12
+ mac-address 00:01:02:03:04:05
+ }
+ static-mapping TEST2-1 {
+ ip-address 192.168.0.21
+ mac-address 00:01:02:03:04:21
+ }
+ static-mapping TEST2-2 {
+ ip-address 192.168.0.21
+ mac-address 00:01:02:03:04:22
+ }
}
}
}
diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py
index 140642806..c49d3e76c 100644
--- a/smoketest/scripts/cli/base_vyostest_shim.py
+++ b/smoketest/scripts/cli/base_vyostest_shim.py
@@ -102,6 +102,29 @@ class VyOSUnitTestSHIM:
ssh_client.close()
return output, error
+ # Verify nftables output
+ def verify_nftables(self, nftables_search, table, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list table {table}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
+ def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''):
+ nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}')
+
+ for search in nftables_search:
+ matched = False
+ for line in nftables_output.split("\n"):
+ if all(item in line for item in search):
+ matched = True
+ break
+ self.assertTrue(not matched if inverse else matched, msg=search)
+
# standard construction; typing suggestion: https://stackoverflow.com/a/70292317
def ignore_warning(warning: Type[Warning]):
import warnings
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index bc2848492..9e8473fa4 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -22,7 +22,6 @@ from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
from vyos.utils.process import run
sysfs_config = {
@@ -67,28 +66,6 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip vyos_filter', inverse=True)
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
- def verify_nftables_chain(self, nftables_search, table, chain, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list chain {table} {chain}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
# Resolver no longer blocks commit, need to wait for daemon to populate set
count = 0
@@ -652,8 +629,8 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
nftables_search = [
['ct state { established, related }', 'accept'],
['ct state invalid', 'reject'],
- ['ct state new', 'ct status == dnat', 'accept'],
- ['ct state { established, new }', 'ct status == snat', 'accept'],
+ ['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'],
@@ -808,7 +785,6 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['ct state related', 'accept']
]
- nftables_output = cmd('sudo nft list table ip vyos_filter')
self.verify_nftables(nftables_search, 'ip vyos_filter')
self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter')
diff --git a/smoketest/scripts/cli/test_high-availability_vrrp.py b/smoketest/scripts/cli/test_high-availability_vrrp.py
index 98259d830..1bb35e422 100755
--- a/smoketest/scripts/cli/test_high-availability_vrrp.py
+++ b/smoketest/scripts/cli/test_high-availability_vrrp.py
@@ -237,5 +237,32 @@ class TestVRRP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'track_interface', config)
self.assertIn(f' {none_vrrp_interface}', config)
+ def test_05_set_multiple_peer_address(self):
+ group = 'VyOS-WAN'
+ vlan_id = '24'
+ vip = '100.64.24.1/24'
+ peer_address_1 = '192.0.2.1'
+ peer_address_2 = '192.0.2.2'
+ vrid = '150'
+ group_base = base_path + ['vrrp', 'group', group]
+
+ self.cli_set(['interfaces', 'ethernet', vrrp_interface, 'vif', vlan_id, 'address', '100.64.24.11/24'])
+ self.cli_set(group_base + ['interface', vrrp_interface])
+ self.cli_set(group_base + ['address', vip])
+ self.cli_set(group_base + ['peer-address', peer_address_1])
+ self.cli_set(group_base + ['peer-address', peer_address_2])
+ self.cli_set(group_base + ['vrid', vrid])
+
+ # commit changes
+ self.cli_commit()
+
+ config = getConfig(f'vrrp_instance {group}')
+
+ self.assertIn(f'interface {vrrp_interface}', config)
+ self.assertIn(f'virtual_router_id {vrid}', config)
+ self.assertIn(f'unicast_peer', config)
+ self.assertIn(f' {peer_address_1}', config)
+ self.assertIn(f' {peer_address_2}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index e414f18cb..8f387b23d 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.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
@@ -17,7 +17,9 @@
import os
import re
import unittest
+
from glob import glob
+from json import loads
from netifaces import AF_INET
from netifaces import AF_INET6
@@ -27,9 +29,9 @@ from base_interfaces_test import BasicInterfaceTest
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.pki import CERT_BEGIN
-from vyos.template import is_ipv6
from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
+from vyos.utils.process import popen
from vyos.utils.file import read_file
from vyos.utils.network import is_ipv6_link_local
@@ -301,5 +303,56 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_delete(['pki', 'ca', name])
self.cli_delete(['pki', 'certificate', cert_name])
+ def test_ethtool_ring_buffer(self):
+ for interface in self._interfaces:
+ # We do not use vyos.ethtool here to not have any chance
+ # for invalid testcases. Re-gain data by hand
+ tmp = cmd(f'sudo ethtool --json --show-ring {interface}')
+ tmp = loads(tmp)
+ max_rx = str(tmp[0]['rx-max'])
+ max_tx = str(tmp[0]['tx-max'])
+
+ self.cli_set(self._base_path + [interface, 'ring-buffer', 'rx', max_rx])
+ self.cli_set(self._base_path + [interface, 'ring-buffer', 'tx', max_tx])
+
+ self.cli_commit()
+
+ for interface in self._interfaces:
+ tmp = cmd(f'sudo ethtool --json --show-ring {interface}')
+ tmp = loads(tmp)
+ max_rx = str(tmp[0]['rx-max'])
+ max_tx = str(tmp[0]['tx-max'])
+ rx = str(tmp[0]['rx'])
+ tx = str(tmp[0]['tx'])
+
+ # validate if the above change was carried out properly and the
+ # ring-buffer size got increased
+ self.assertEqual(max_rx, rx)
+ self.assertEqual(max_tx, tx)
+
+ def test_ethtool_flow_control(self):
+ for interface in self._interfaces:
+ # Disable flow-control
+ self.cli_set(self._base_path + [interface, 'disable-flow-control'])
+ # Check current flow-control state on ethernet interface
+ out, err = popen(f'sudo ethtool --json --show-pause {interface}')
+ # Flow-control not supported - test if it bails out with a proper
+ # this is a dynamic path where err = 1 on VMware, but err = 0 on
+ # a physical box.
+ if bool(err):
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ else:
+ out = loads(out)
+ # Flow control is on
+ self.assertTrue(out[0]['autonegotiate'])
+
+ # commit change on CLI to disable-flow-control and re-test
+ self.cli_commit()
+
+ out, err = popen(f'sudo ethtool --json --show-pause {interface}')
+ out = loads(out)
+ self.assertFalse(out[0]['autonegotiate'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 1e6435df8..43e374398 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -21,8 +21,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
-from vyos.utils.dict import dict_search
base_path = ['nat']
src_path = base_path + ['source']
@@ -47,17 +45,6 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.assertFalse(os.path.exists(nftables_nat_config))
self.assertFalse(os.path.exists(nftables_static_nat_conf))
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def wait_for_domain_resolver(self, table, set_name, element, max_wait=10):
# Resolver no longer blocks commit, need to wait for daemon to populate set
count = 0
@@ -100,21 +87,28 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
address_group_member = '192.0.2.1'
interface_group = 'smoketest_ifaces'
interface_group_member = 'bond.99'
- rule = '100'
self.cli_set(['firewall', 'group', 'address-group', address_group, 'address', address_group_member])
self.cli_set(['firewall', 'group', 'interface-group', interface_group, 'interface', interface_group_member])
- self.cli_set(src_path + ['rule', rule, 'source', 'group', 'address-group', address_group])
- self.cli_set(src_path + ['rule', rule, 'outbound-interface', 'group', interface_group])
- self.cli_set(src_path + ['rule', rule, 'translation', 'address', 'masquerade'])
+ self.cli_set(src_path + ['rule', '100', 'source', 'group', 'address-group', address_group])
+ self.cli_set(src_path + ['rule', '100', 'outbound-interface', 'group', interface_group])
+ self.cli_set(src_path + ['rule', '100', 'translation', 'address', 'masquerade'])
+
+ self.cli_set(src_path + ['rule', '110', 'source', 'group', 'address-group', address_group])
+ self.cli_set(src_path + ['rule', '110', 'translation', 'address', '203.0.113.1'])
+
+ self.cli_set(src_path + ['rule', '120', 'source', 'group', 'address-group', address_group])
+ self.cli_set(src_path + ['rule', '120', 'translation', 'address', '203.0.113.111/32'])
self.cli_commit()
nftables_search = [
[f'set A_{address_group}'],
[f'elements = {{ {address_group_member} }}'],
- [f'ip saddr @A_{address_group}', f'oifname @I_{interface_group}', 'masquerade']
+ [f'ip saddr @A_{address_group}', f'oifname @I_{interface_group}', 'masquerade'],
+ [f'ip saddr @A_{address_group}', 'snat to 203.0.113.1'],
+ [f'ip saddr @A_{address_group}', 'snat prefix to 203.0.113.111/32']
]
self.verify_nftables(nftables_search, 'ip vyos_nat')
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 0607f6616..400a895ff 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -22,8 +22,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
-from vyos.utils.process import cmd
-from vyos.utils.dict import dict_search
base_path = ['nat66']
src_path = base_path + ['source']
@@ -42,17 +40,6 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def test_source_nat66(self):
source_prefix = 'fc00::/64'
translation_prefix = 'fc01::/64'
diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py
index c0b7c1fe7..462fc24d0 100755
--- a/smoketest/scripts/cli/test_policy_route.py
+++ b/smoketest/scripts/cli/test_policy_route.py
@@ -68,17 +68,6 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase):
self.verify_rules(ip_rule_search, inverse=True)
- def verify_nftables(self, nftables_search, table, inverse=False):
- nftables_output = cmd(f'sudo nft list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def verify_rules(self, rules_search, inverse=False):
rule_output = cmd('ip rule show')
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 6bffc7c45..82fb96754 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.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
@@ -240,7 +240,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
def test_ospf_07_redistribute(self):
metric = '15'
metric_type = '1'
- redistribute = ['bgp', 'connected', 'isis', 'kernel', 'rip', 'static']
+ redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']
for protocol in redistribute:
self.cli_set(base_path + ['redistribute', protocol, 'metric', metric])
diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py
index 4ae7f05d9..989e1552d 100755
--- a/smoketest/scripts/cli/test_protocols_ospfv3.py
+++ b/smoketest/scripts/cli/test_protocols_ospfv3.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
@@ -114,14 +114,18 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
def test_ospfv3_03_redistribute(self):
+ metric = '15'
+ metric_type = '1'
route_map = 'foo-bar'
route_map_seq = '10'
- redistribute = ['bgp', 'connected', 'kernel', 'ripng', 'static']
+ redistribute = ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']
self.cli_set(['policy', 'route-map', route_map, 'rule', route_map_seq, 'action', 'permit'])
for protocol in redistribute:
+ self.cli_set(base_path + ['redistribute', protocol, 'metric', metric])
self.cli_set(base_path + ['redistribute', protocol, 'route-map', route_map])
+ self.cli_set(base_path + ['redistribute', protocol, 'metric-type', metric_type])
# commit changes
self.cli_commit()
@@ -130,7 +134,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase):
frrconfig = self.getFRRconfig('router ospf6', daemon=PROCESS_NAME)
self.assertIn(f'router ospf6', frrconfig)
for protocol in redistribute:
- self.assertIn(f' redistribute {protocol} route-map {route_map}', frrconfig)
+ self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig)
def test_ospfv3_04_interfaces(self):
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 194289567..b582a2108 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -100,6 +100,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
pool = base_path + ['shared-network-name', shared_net_name, 'subnet', subnet]
self.cli_set(pool + ['subnet-id', '1'])
+ self.cli_set(pool + ['ignore-client-id'])
# we use the first subnet IP address as default gateway
self.cli_set(pool + ['option', 'default-router', router])
self.cli_set(pool + ['option', 'name-server', dns_1])
@@ -124,6 +125,7 @@ class TestServiceDHCPServer(VyOSUnitTestSHIM.TestCase):
self.verify_config_value(obj, ['Dhcp4', 'shared-networks'], 'name', shared_net_name)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'subnet', subnet)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'id', 1)
+ self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'match-client-id', False)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'valid-lifetime', 86400)
self.verify_config_value(obj, ['Dhcp4', 'shared-networks', 0, 'subnet4'], 'max-valid-lifetime', 86400)
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index ce237a6e7..f00626b3d 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -21,7 +21,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.firewall import find_nftables_rule
-from vyos.utils.process import cmd
from vyos.utils.file import read_file
base_path = ['system', 'conntrack']
@@ -43,17 +42,6 @@ class TestSystemConntrack(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
- def verify_nftables(self, nftables_search, table, inverse=False, args=''):
- nftables_output = cmd(f'sudo nft {args} list table {table}')
-
- for search in nftables_search:
- matched = False
- for line in nftables_output.split("\n"):
- if all(item in line for item in search):
- matched = True
- break
- self.assertTrue(not matched if inverse else matched, msg=search)
-
def test_conntrack_options(self):
conntrack_config = {
'net.netfilter.nf_conntrack_expect_max' : {
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index 438387f2d..c96b8e374 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -529,5 +529,28 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.assertNotIn(f' no ip nht resolve-via-default', frrconfig)
self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig)
+ def test_vrf_conntrack(self):
+ table = '1000'
+ nftables_rules = {
+ 'vrf_zones_ct_in': ['ct original zone set iifname map @ct_iface_map'],
+ 'vrf_zones_ct_out': ['ct original zone set oifname map @ct_iface_map']
+ }
+
+ self.cli_set(base_path + ['name', 'blue', 'table', table])
+ self.cli_commit()
+
+ # Conntrack rules should not be present
+ for chain, rule in nftables_rules.items():
+ self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=True)
+
+ self.cli_set(['nat'])
+ self.cli_commit()
+
+ # Conntrack rules should now be present
+ for chain, rule in nftables_rules.items():
+ self.verify_nftables_chain(rule, 'inet vrf_zones', chain, inverse=False)
+
+ self.cli_delete(['nat'])
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 321d00abf..e967bee71 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.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
@@ -32,7 +32,6 @@ from vyos.utils.file import write_file
from vyos.utils.process import call
from vyos.utils.process import cmd
from vyos.utils.process import run
-from vyos.utils.process import rc_cmd
from vyos.template import bracketize_ipv6
from vyos.template import inc_ip
from vyos.template import is_ipv4
@@ -251,7 +250,7 @@ def verify(container):
if 'authentication' not in registry_config:
continue
if not {'username', 'password'} <= set(registry_config['authentication']):
- raise ConfigError('If registry username or or password is defined, so must be the other!')
+ raise ConfigError('Container registry requires both username and password to be set!')
return None
@@ -401,24 +400,6 @@ def generate(container):
write_file(f'/etc/containers/networks/{network}.json', json_write(tmp, indent=2))
- if 'registry' in container:
- cmd = f'podman logout --all'
- rc, out = rc_cmd(cmd)
- if rc != 0:
- raise ConfigError(out)
-
- for registry, registry_config in container['registry'].items():
- if 'disable' in registry_config:
- continue
- if 'authentication' in registry_config:
- if {'username', 'password'} <= set(registry_config['authentication']):
- username = registry_config['authentication']['username']
- password = registry_config['authentication']['password']
- cmd = f'podman login --username {username} --password {password} {registry}'
- rc, out = rc_cmd(cmd)
- if rc != 0:
- raise ConfigError(out)
-
render(config_containers, 'container/containers.conf.j2', container)
render(config_registry, 'container/registries.conf.j2', container)
render(config_storage, 'container/storage.conf.j2', container)
diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py
index acb7dfa41..3c27655b0 100755
--- a/src/conf_mode/firewall.py
+++ b/src/conf_mode/firewall.py
@@ -282,6 +282,15 @@ def verify_rule(firewall, rule_conf, ipv6):
if direction in rule_conf:
if 'name' in rule_conf[direction] and 'group' in rule_conf[direction]:
raise ConfigError(f'Cannot specify both interface group and interface name for {direction}')
+ if 'group' in rule_conf[direction]:
+ group_name = rule_conf[direction]['group']
+ if group_name[0] == '!':
+ group_name = group_name[1:]
+ group_obj = dict_search_args(firewall, 'group', 'interface_group', group_name)
+ if group_obj is None:
+ raise ConfigError(f'Invalid interface group "{group_name}" on firewall rule')
+ if not group_obj:
+ Warning(f'interface-group "{group_name}" has no members!')
def verify_nested_group(group_name, group, groups, seen):
if 'include' not in group:
diff --git a/src/conf_mode/high-availability.py b/src/conf_mode/high-availability.py
index b3b27b14e..59d49ea67 100755
--- a/src/conf_mode/high-availability.py
+++ b/src/conf_mode/high-availability.py
@@ -125,8 +125,9 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" uses IPv4 but hello-source-address is IPv6!')
if 'peer_address' in group_config:
- if is_ipv6(group_config['peer_address']):
- raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
+ for peer_address in group_config['peer_address']:
+ if is_ipv6(peer_address):
+ raise ConfigError(f'VRRP group "{group}" uses IPv4 but peer-address is IPv6!')
if vaddrs6:
tmp = {'interface': interface, 'vrid': vrid, 'ipver': 'IPv6'}
@@ -139,8 +140,9 @@ def verify(ha):
raise ConfigError(f'VRRP group "{group}" uses IPv6 but hello-source-address is IPv4!')
if 'peer_address' in group_config:
- if is_ipv4(group_config['peer_address']):
- raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
+ for peer_address in group_config['peer_address']:
+ if is_ipv4(peer_address):
+ raise ConfigError(f'VRRP group "{group}" uses IPv6 but peer-address is IPv4!')
# Check sync groups
if 'vrrp' in ha and 'sync_group' in ha['vrrp']:
for sync_group, sync_config in ha['vrrp']['sync_group'].items():
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 26822b755..b3f38c04a 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -153,6 +153,15 @@ def verify(nat):
elif 'name' in config['outbound_interface']:
if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
Warning(f'NAT interface "{config["outbound_interface"]["name"]}" for source NAT rule "{rule}" does not exist!')
+ else:
+ group_name = config['outbound_interface']['group']
+ if group_name[0] == '!':
+ group_name = group_name[1:]
+ group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name)
+ if group_obj is None:
+ raise ConfigError(f'Invalid interface group "{group_name}" on source nat rule')
+ if not group_obj:
+ Warning(f'interface-group "{group_name}" has no members!')
if not dict_search('translation.address', config) and not dict_search('translation.port', config):
if 'exclude' not in config and 'backend' not in config['load_balance']:
@@ -177,6 +186,15 @@ def verify(nat):
elif 'name' in config['inbound_interface']:
if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
Warning(f'NAT interface "{config["inbound_interface"]["name"]}" for destination NAT rule "{rule}" does not exist!')
+ else:
+ group_name = config['inbound_interface']['group']
+ if group_name[0] == '!':
+ group_name = group_name[1:]
+ group_obj = dict_search_args(nat['firewall_group'], 'interface_group', group_name)
+ if group_obj is None:
+ raise ConfigError(f'Invalid interface group "{group_name}" on destination nat rule')
+ if not group_obj:
+ Warning(f'interface-group "{group_name}" has no members!')
if not dict_search('translation.address', config) and not dict_search('translation.port', config) and 'redirect' not in config['translation']:
if 'exclude' not in config and 'backend' not in config['load_balance']:
diff --git a/src/conf_mode/protocols_ospf.py b/src/conf_mode/protocols_ospf.py
index 34cf49286..695842795 100755
--- a/src/conf_mode/protocols_ospf.py
+++ b/src/conf_mode/protocols_ospf.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
@@ -91,6 +91,8 @@ def get_config(config=None):
for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'rip', 'static']:
if dict_search(f'redistribute.{protocol}', ospf) is None:
del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
for interface in ospf.get('interface', []):
# We need to reload the defaults on every pass b/c of
@@ -213,7 +215,7 @@ def verify(ospf):
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', ospf):
g_high_label_value = dict_search('segment_routing.global_block.high_label_value', ospf)
g_low_label_value = dict_search('segment_routing.global_block.low_label_value', ospf)
diff --git a/src/conf_mode/protocols_ospfv3.py b/src/conf_mode/protocols_ospfv3.py
index 5b1adce30..afd767dbf 100755
--- a/src/conf_mode/protocols_ospfv3.py
+++ b/src/conf_mode/protocols_ospfv3.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
@@ -85,6 +85,12 @@ def get_config(config=None):
if 'graceful_restart' not in ospfv3:
del default_values['graceful_restart']
+ for protocol in ['babel', 'bgp', 'connected', 'isis', 'kernel', 'ripng', 'static']:
+ if dict_search(f'redistribute.{protocol}', ospfv3) is None:
+ del default_values['redistribute'][protocol]
+ if not bool(default_values['redistribute']):
+ del default_values['redistribute']
+
default_values.pop('interface', {})
# merge in remaining default values
diff --git a/src/conf_mode/system_conntrack.py b/src/conf_mode/system_conntrack.py
index 7f6c71440..e075bc928 100755
--- a/src/conf_mode/system_conntrack.py
+++ b/src/conf_mode/system_conntrack.py
@@ -104,6 +104,10 @@ def get_config(config=None):
if conf.exists(['service', 'conntrack-sync']):
set_dependents('conntrack_sync', conf)
+ # If conntrack status changes, VRF zone rules need updating
+ if conf.exists(['vrf']):
+ set_dependents('vrf', conf)
+
return conntrack
def verify(conntrack):
diff --git a/src/conf_mode/system_login_banner.py b/src/conf_mode/system_login_banner.py
index 65fa04417..923e1bf57 100755
--- a/src/conf_mode/system_login_banner.py
+++ b/src/conf_mode/system_login_banner.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2021 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
@@ -18,30 +18,26 @@ from sys import exit
from copy import deepcopy
from vyos.config import Config
+from vyos.template import render
from vyos.utils.file import write_file
+from vyos.version import get_version_data
from vyos import ConfigError
from vyos import airbag
airbag.enable()
-try:
- with open('/usr/share/vyos/default_motd') as f:
- motd = f.read()
-except:
- # Use an empty banner if the default banner file cannot be read
- motd = "\n"
-
PRELOGIN_FILE = r'/etc/issue'
PRELOGIN_NET_FILE = r'/etc/issue.net'
POSTLOGIN_FILE = r'/etc/motd'
default_config_data = {
'issue': 'Welcome to VyOS - \\n \\l\n\n',
- 'issue_net': '',
- 'motd': motd
+ 'issue_net': ''
}
def get_config(config=None):
banner = deepcopy(default_config_data)
+ banner['version_data'] = get_version_data()
+
if config:
conf = config
else:
@@ -92,7 +88,11 @@ def generate(banner):
def apply(banner):
write_file(PRELOGIN_FILE, banner['issue'])
write_file(PRELOGIN_NET_FILE, banner['issue_net'])
- write_file(POSTLOGIN_FILE, banner['motd'])
+ if 'motd' in banner:
+ write_file(POSTLOGIN_FILE, banner['motd'])
+ else:
+ render(POSTLOGIN_FILE, 'login/default_motd.j2', banner,
+ permission=0o644, user='root', group='root')
return None
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 3b5b67437..7ed451e16 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -92,6 +92,8 @@ def generate(options):
if 'kernel' in options:
if 'disable_mitigations' in options['kernel']:
cmdline_options.append('mitigations=off')
+ if 'disable_power_saving' in options['kernel']:
+ cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1')
grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))
return None
diff --git a/src/conf_mode/vpn_ipsec.py b/src/conf_mode/vpn_ipsec.py
index d074ed159..388f2a709 100755
--- a/src/conf_mode/vpn_ipsec.py
+++ b/src/conf_mode/vpn_ipsec.py
@@ -25,6 +25,8 @@ from time import time
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdep import set_dependents
+from vyos.configdep import call_dependents
from vyos.configdict import leaf_node_changed
from vyos.configverify import verify_interface_exists
from vyos.configverify import dynamic_interface_pattern
@@ -97,6 +99,9 @@ def get_config(config=None):
ipsec['interface_change'] = leaf_node_changed(conf, base + ['interface'])
ipsec['nhrp_exists'] = conf.exists(['protocols', 'nhrp', 'tunnel'])
+ if ipsec['nhrp_exists']:
+ set_dependents('nhrp', conf)
+
tmp = conf.get_config_dict(l2tp_base, key_mangling=('-', '_'),
no_tag_node_value_mangle=True,
get_first_key=True)
@@ -575,13 +580,6 @@ def generate(ipsec):
render(interface_conf, 'ipsec/interfaces_use.conf.j2', ipsec)
render(swanctl_conf, 'ipsec/swanctl.conf.j2', ipsec)
-def resync_nhrp(ipsec):
- if ipsec and not ipsec['nhrp_exists']:
- return
-
- tmp = run('/usr/libexec/vyos/conf_mode/protocols_nhrp.py')
- if tmp > 0:
- print('ERROR: failed to reapply NHRP settings!')
def apply(ipsec):
systemd_service = 'strongswan.service'
@@ -590,7 +588,14 @@ def apply(ipsec):
else:
call(f'systemctl reload-or-restart {systemd_service}')
- resync_nhrp(ipsec)
+ if ipsec.get('nhrp_exists', False):
+ try:
+ call_dependents()
+ except ConfigError:
+ # Ignore config errors on dependent due to being called too early. Example:
+ # ConfigError("ConfigError('Interface ethN requires an IP address!')")
+ pass
+
if __name__ == '__main__':
try:
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index a2f4956be..16908100f 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -23,6 +23,7 @@ from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
+from vyos.firewall import conntrack_required
from vyos.ifconfig import Interface
from vyos.template import render
from vyos.template import render_to_string
@@ -41,6 +42,12 @@ airbag.enable()
config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf'
k_mod = ['vrf']
+nftables_table = 'inet vrf_zones'
+nftables_rules = {
+ 'vrf_zones_ct_in': 'counter ct original zone set iifname map @ct_iface_map',
+ 'vrf_zones_ct_out': 'counter ct original zone set oifname map @ct_iface_map'
+}
+
def has_rule(af : str, priority : int, table : str=None):
"""
Check if a given ip rule exists
@@ -114,6 +121,9 @@ def get_config(config=None):
routes = vrf_routing(conf, name)
if routes: vrf['vrf_remove'][name]['route'] = routes
+ if 'name' in vrf:
+ vrf['conntrack'] = conntrack_required(conf)
+
# We also need the route-map information from the config
#
# XXX: one MUST always call this without the key_mangling() option! See
@@ -294,6 +304,14 @@ def apply(vrf):
nft_add_element = f'add element inet vrf_zones ct_iface_map {{ "{name}" : {table} }}'
cmd(f'nft {nft_add_element}')
+ if vrf['conntrack']:
+ for chain, rule in nftables_rules.items():
+ cmd(f'nft add rule inet vrf_zones {chain} {rule}')
+
+ if 'name' not in vrf or not vrf['conntrack']:
+ for chain, rule in nftables_rules.items():
+ cmd(f'nft flush chain inet vrf_zones {chain}')
+
# Apply FRR filters
zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
diff --git a/src/init/vyos-router b/src/init/vyos-router
index 74e3ca51b..adf892371 100755
--- a/src/init/vyos-router
+++ b/src/init/vyos-router
@@ -281,8 +281,8 @@ cleanup_post_commit_hooks () {
# note that this approach only supports hooks that are "configured",
# i.e., it does not support hooks that need to always be present.
cpostdir=$(cli-shell-api getPostCommitHookDir)
- # exclude commits hooks from vyatta-cfg
- excluded="10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
+ # exclude commit hooks that need to always be present
+ excluded="00vyos-sync 10vyatta-log-commit.pl 99vyos-user-postcommit-hooks"
if [ -d "$cpostdir" ]; then
for f in $cpostdir/*; do
if [[ ! $excluded =~ $(basename $f) ]]; then
diff --git a/src/migration-scripts/dhcp-server/6-to-7 b/src/migration-scripts/dhcp-server/6-to-7
index ccf385a30..e6c298a60 100755
--- a/src/migration-scripts/dhcp-server/6-to-7
+++ b/src/migration-scripts/dhcp-server/6-to-7
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2023 VyOS maintainers and contributors
+# 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
@@ -14,19 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T3316: Migrate to Kea
-# - global-parameters will not function
-# - shared-network-parameters will not function
-# - subnet-parameters will not function
-# - static-mapping-parameters will not function
-# - host-decl-name is on by default, option removed
-# - ping-check no longer supported
-# - failover is default enabled on all subnets that exist on failover servers
+# T6079: Disable duplicate static mappings
import sys
from vyos.configtree import ConfigTree
-if (len(sys.argv) < 2):
+if len(sys.argv) < 2:
print("Must specify file name!")
sys.exit(1)
@@ -38,46 +31,42 @@ with open(file_name, 'r') as f:
base = ['service', 'dhcp-server']
config = ConfigTree(config_file)
-if not config.exists(base):
+if not config.exists(base + ['shared-network-name']):
# Nothing to do
- sys.exit(0)
+ exit(0)
-if config.exists(base + ['host-decl-name']):
- config.delete(base + ['host-decl-name'])
+# Run this for every instance if 'shared-network-name'
+for network in config.list_nodes(base + ['shared-network-name']):
+ base_network = base + ['shared-network-name', network]
-if config.exists(base + ['global-parameters']):
- config.delete(base + ['global-parameters'])
+ if not config.exists(base_network + ['subnet']):
+ continue
-if config.exists(base + ['shared-network-name']):
- for network in config.list_nodes(base + ['shared-network-name']):
- base_network = base + ['shared-network-name', network]
+ for subnet in config.list_nodes(base_network + ['subnet']):
+ base_subnet = base_network + ['subnet', subnet]
- if config.exists(base_network + ['ping-check']):
- config.delete(base_network + ['ping-check'])
+ if config.exists(base_subnet + ['static-mapping']):
+ used_mac = []
+ used_ip = []
- if config.exists(base_network + ['shared-network-parameters']):
- config.delete(base_network +['shared-network-parameters'])
+ for mapping in config.list_nodes(base_subnet + ['static-mapping']):
+ base_mapping = base_subnet + ['static-mapping', mapping]
- if not config.exists(base_network + ['subnet']):
- continue
+ if config.exists(base_mapping + ['mac-address']):
+ mac = config.return_value(base_mapping + ['mac-address'])
- # Run this for every specified 'subnet'
- for subnet in config.list_nodes(base_network + ['subnet']):
- base_subnet = base_network + ['subnet', subnet]
+ if mac in used_mac:
+ config.set(base_mapping + ['disable'])
+ else:
+ used_mac.append(mac)
- if config.exists(base_subnet + ['enable-failover']):
- config.delete(base_subnet + ['enable-failover'])
+ if config.exists(base_mapping + ['ip-address']):
+ ip = config.return_value(base_mapping + ['ip-address'])
- if config.exists(base_subnet + ['ping-check']):
- config.delete(base_subnet + ['ping-check'])
-
- if config.exists(base_subnet + ['subnet-parameters']):
- config.delete(base_subnet + ['subnet-parameters'])
-
- if config.exists(base_subnet + ['static-mapping']):
- for mapping in config.list_nodes(base_subnet + ['static-mapping']):
- if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']):
- config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters'])
+ if ip in used_ip:
+ config.set(base_subnet + ['static-mapping', mapping, 'disable'])
+ else:
+ used_ip.append(ip)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dhcp-server/7-to-8 b/src/migration-scripts/dhcp-server/7-to-8
index 151aa6d7b..ccf385a30 100755
--- a/src/migration-scripts/dhcp-server/7-to-8
+++ b/src/migration-scripts/dhcp-server/7-to-8
@@ -14,16 +14,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-# T3316:
-# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore)
-# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..."
-# to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..."
+# T3316: Migrate to Kea
+# - global-parameters will not function
+# - shared-network-parameters will not function
+# - subnet-parameters will not function
+# - static-mapping-parameters will not function
+# - host-decl-name is on by default, option removed
+# - ping-check no longer supported
+# - failover is default enabled on all subnets that exist on failover servers
import sys
-import re
from vyos.configtree import ConfigTree
-if len(sys.argv) < 2:
+if (len(sys.argv) < 2):
print("Must specify file name!")
sys.exit(1)
@@ -32,30 +35,49 @@ file_name = sys.argv[1]
with open(file_name, 'r') as f:
config_file = f.read()
-base = ['service', 'dhcp-server', 'shared-network-name']
+base = ['service', 'dhcp-server']
config = ConfigTree(config_file)
if not config.exists(base):
# Nothing to do
sys.exit(0)
-for network in config.list_nodes(base):
- # Run this for every specified 'subnet'
- if config.exists(base + [network, 'subnet']):
- for subnet in config.list_nodes(base + [network, 'subnet']):
- base_subnet = base + [network, 'subnet', subnet]
- if config.exists(base_subnet + ['static-mapping']):
- for hostname in config.list_nodes(base_subnet + ['static-mapping']):
- base_mapping = base_subnet + ['static-mapping', hostname]
+if config.exists(base + ['host-decl-name']):
+ config.delete(base + ['host-decl-name'])
+
+if config.exists(base + ['global-parameters']):
+ config.delete(base + ['global-parameters'])
+
+if config.exists(base + ['shared-network-name']):
+ for network in config.list_nodes(base + ['shared-network-name']):
+ base_network = base + ['shared-network-name', network]
+
+ if config.exists(base_network + ['ping-check']):
+ config.delete(base_network + ['ping-check'])
+
+ if config.exists(base_network + ['shared-network-parameters']):
+ config.delete(base_network +['shared-network-parameters'])
- # Rename the 'mac-address' node to 'mac'
- if config.exists(base_mapping + ['mac-address']):
- config.rename(base_mapping + ['mac-address'], 'mac')
+ if not config.exists(base_network + ['subnet']):
+ continue
- # Adjust hostname to have valid FQDN characters only
- new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname)
- if new_hostname != hostname:
- config.rename(base_mapping, new_hostname)
+ # Run this for every specified 'subnet'
+ for subnet in config.list_nodes(base_network + ['subnet']):
+ base_subnet = base_network + ['subnet', subnet]
+
+ if config.exists(base_subnet + ['enable-failover']):
+ config.delete(base_subnet + ['enable-failover'])
+
+ if config.exists(base_subnet + ['ping-check']):
+ config.delete(base_subnet + ['ping-check'])
+
+ if config.exists(base_subnet + ['subnet-parameters']):
+ config.delete(base_subnet + ['subnet-parameters'])
+
+ if config.exists(base_subnet + ['static-mapping']):
+ for mapping in config.list_nodes(base_subnet + ['static-mapping']):
+ if config.exists(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters']):
+ config.delete(base_subnet + ['static-mapping', mapping, 'static-mapping-parameters'])
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dhcp-server/8-to-9 b/src/migration-scripts/dhcp-server/8-to-9
index 810e403a6..151aa6d7b 100755
--- a/src/migration-scripts/dhcp-server/8-to-9
+++ b/src/migration-scripts/dhcp-server/8-to-9
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2024 VyOS maintainers and contributors
+# Copyright (C) 2023 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -15,8 +15,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# T3316:
-# - Migrate dhcp options under new option node
-# - Add subnet IDs to existing subnets
+# - Adjust hostname to have valid FQDN characters only (underscores aren't allowed anymore)
+# - Rename "service dhcp-server shared-network-name ... static-mapping <hostname> mac-address ..."
+# to "service dhcp-server shared-network-name ... static-mapping <hostname> mac ..."
import sys
import re
@@ -38,34 +39,23 @@ if not config.exists(base):
# Nothing to do
sys.exit(0)
-option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal',
- 'client-prefix-length', 'default-router', 'domain-name', 'domain-search',
- 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server',
- 'pop-server', 'server-identifier', 'smtp-server', 'static-route',
- 'tftp-server-name', 'time-offset', 'time-server', 'time-zone',
- 'vendor-option', 'wins-server', 'wpad-url']
-
-subnet_id = 1
-
for network in config.list_nodes(base):
- for option in option_nodes:
- if config.exists(base + [network, option]):
- config.set(base + [network, 'option'])
- config.copy(base + [network, option], base + [network, 'option', option])
- config.delete(base + [network, option])
-
+ # Run this for every specified 'subnet'
if config.exists(base + [network, 'subnet']):
for subnet in config.list_nodes(base + [network, 'subnet']):
base_subnet = base + [network, 'subnet', subnet]
-
- for option in option_nodes:
- if config.exists(base_subnet + [option]):
- config.set(base_subnet + ['option'])
- config.copy(base_subnet + [option], base_subnet + ['option', option])
- config.delete(base_subnet + [option])
+ if config.exists(base_subnet + ['static-mapping']):
+ for hostname in config.list_nodes(base_subnet + ['static-mapping']):
+ base_mapping = base_subnet + ['static-mapping', hostname]
+
+ # Rename the 'mac-address' node to 'mac'
+ if config.exists(base_mapping + ['mac-address']):
+ config.rename(base_mapping + ['mac-address'], 'mac')
- config.set(base_subnet + ['subnet-id'], value=subnet_id)
- subnet_id += 1
+ # Adjust hostname to have valid FQDN characters only
+ new_hostname = re.sub(r'[^a-zA-Z0-9-.]', '-', hostname)
+ if new_hostname != hostname:
+ config.rename(base_mapping, new_hostname)
try:
with open(file_name, 'w') as f:
diff --git a/src/migration-scripts/dhcp-server/9-to-10 b/src/migration-scripts/dhcp-server/9-to-10
new file mode 100755
index 000000000..810e403a6
--- /dev/null
+++ b/src/migration-scripts/dhcp-server/9-to-10
@@ -0,0 +1,75 @@
+#!/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/>.
+
+# T3316:
+# - Migrate dhcp options under new option node
+# - Add subnet IDs to existing subnets
+
+import sys
+import re
+from vyos.configtree import ConfigTree
+
+if len(sys.argv) < 2:
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['service', 'dhcp-server', 'shared-network-name']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ # Nothing to do
+ sys.exit(0)
+
+option_nodes = ['bootfile-name', 'bootfile-server', 'bootfile-size', 'captive-portal',
+ 'client-prefix-length', 'default-router', 'domain-name', 'domain-search',
+ 'name-server', 'ip-forwarding', 'ipv6-only-preferred', 'ntp-server',
+ 'pop-server', 'server-identifier', 'smtp-server', 'static-route',
+ 'tftp-server-name', 'time-offset', 'time-server', 'time-zone',
+ 'vendor-option', 'wins-server', 'wpad-url']
+
+subnet_id = 1
+
+for network in config.list_nodes(base):
+ for option in option_nodes:
+ if config.exists(base + [network, option]):
+ config.set(base + [network, 'option'])
+ config.copy(base + [network, option], base + [network, 'option', option])
+ config.delete(base + [network, option])
+
+ if config.exists(base + [network, 'subnet']):
+ for subnet in config.list_nodes(base + [network, 'subnet']):
+ base_subnet = base + [network, 'subnet', subnet]
+
+ for option in option_nodes:
+ if config.exists(base_subnet + [option]):
+ config.set(base_subnet + ['option'])
+ config.copy(base_subnet + [option], base_subnet + ['option', option])
+ config.delete(base_subnet + [option])
+
+ config.set(base_subnet + ['subnet-id'], value=subnet_id)
+ subnet_id += 1
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index 5a022d0c0..d29af8821 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2022 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
@@ -20,6 +20,8 @@ import sys
from sys import exit
from vyos.utils.process import cmd
+from vyos.utils.process import call
+from vyos.utils.process import rc_cmd
import vyos.opmode
@@ -29,23 +31,51 @@ def _get_json_data(command: str) -> list:
"""
return cmd(f'{command} --format json')
-
def _get_raw_data(command: str) -> list:
json_data = _get_json_data(command)
data = json.loads(json_data)
return data
def add_image(name: str):
- from vyos.utils.process import rc_cmd
+ """ Pull image from container registry. If registry authentication
+ is defined within VyOS CLI, credentials are used to login befroe pull """
+ from vyos.configquery import ConfigTreeQuery
+
+ conf = ConfigTreeQuery()
+ container = conf.get_config_dict(['container', 'registry'])
+
+ do_logout = False
+ if 'registry' in container:
+ for registry, registry_config in container['registry'].items():
+ if 'disable' in registry_config:
+ continue
+ if 'authentication' in registry_config:
+ do_logout = True
+ if {'username', 'password'} <= set(registry_config['authentication']):
+ username = registry_config['authentication']['username']
+ password = registry_config['authentication']['password']
+ cmd = f'podman login --username {username} --password {password} {registry}'
+ rc, out = rc_cmd(cmd)
+ if rc != 0: raise vyos.opmode.InternalError(out)
rc, output = rc_cmd(f'podman image pull {name}')
if rc != 0:
raise vyos.opmode.InternalError(output)
+ if do_logout:
+ rc_cmd('podman logout --all')
+
def delete_image(name: str):
from vyos.utils.process import rc_cmd
- rc, output = rc_cmd(f'podman image rm --force {name}')
+ if name == 'all':
+ # gather list of all images and pass them to the removal list
+ name = cmd('sudo podman image ls --quiet')
+ # If there are no container images left, we can not delete them all
+ if not name: return
+ # replace newline with whitespace
+ name = name.replace('\n', ' ')
+ rc, output = rc_cmd(f'podman image rm {name}')
if rc != 0:
raise vyos.opmode.InternalError(output)
@@ -57,7 +87,6 @@ def show_container(raw: bool):
else:
return cmd(command)
-
def show_image(raw: bool):
command = 'podman image ls'
container_data = _get_raw_data('podman image ls')
@@ -66,7 +95,6 @@ def show_image(raw: bool):
else:
return cmd(command)
-
def show_network(raw: bool):
command = 'podman network ls'
container_data = _get_raw_data(command)
@@ -75,7 +103,6 @@ def show_network(raw: bool):
else:
return cmd(command)
-
def restart(name: str):
from vyos.utils.process import rc_cmd
@@ -86,7 +113,6 @@ def restart(name: str):
print(f'Container "{name}" restarted!')
return output
-
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 355182b26..648a017d5 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2023 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
@@ -19,6 +19,7 @@ import sys
import grp
import re
import json
+import typing
import logging
import signal
import importlib.util
@@ -29,6 +30,7 @@ from vyos.defaults import directories
from vyos.utils.boot import boot_configuration_complete
from vyos.configsource import ConfigSourceString
from vyos.configsource import ConfigSourceError
+from vyos.configdep import call_dependents
from vyos.config import Config
from vyos import ConfigError
@@ -198,10 +200,12 @@ def initialization(socket):
return None
config = Config(config_source=configsource)
+ dependent_func: dict[str, list[typing.Callable]] = {}
+ setattr(config, 'dependent_func', dependent_func)
return config
-def process_node_data(config, data) -> int:
+def process_node_data(config, data, last: bool = False) -> int:
if not config:
logger.critical(f"Empty config")
return R_ERROR_DAEMON
@@ -223,11 +227,18 @@ def process_node_data(config, data) -> int:
args.insert(0, f'{script_name}.py')
if script_name not in include_set:
+ # call dependents now if last element of prio queue is run
+ # independent of configd
+ if last:
+ call_dependents(dependent_func=config.dependent_func)
return R_PASS
with stdout_redirected(session_out, session_mode):
result = run_script(conf_mode_scripts[script_name], config, args)
+ if last:
+ call_dependents(dependent_func=config.dependent_func)
+
return result
def remove_if_file(f: str):
@@ -281,7 +292,9 @@ if __name__ == '__main__':
socket.send(resp.encode())
config = initialization(socket)
elif message["type"] == "node":
- res = process_node_data(config, message["data"])
+ if message["last"]:
+ logger.debug(f'final element of priority queue')
+ res = process_node_data(config, message["data"], message["last"])
response = res.to_bytes(1, byteorder=sys.byteorder)
logger.debug(f"Sending response {res}")
socket.send(response)
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 40d442e30..a7b14a1a3 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -459,7 +459,6 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
request: Request, background_tasks: BackgroundTasks):
session = app.state.vyos_session
env = session.get_session_env()
- config = Config(session_env=env)
endpoint = request.url.path
@@ -474,6 +473,8 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
# so the lock is really global
lock.acquire()
+ config = Config(session_env=env)
+
status = 200
msg = None
error_msg = None
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index cae8b6152..41723e7a4 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 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
@@ -49,6 +49,7 @@
#define GET_SESSION "cli-shell-api --show-working-only --show-show-defaults --show-ignore-edit showConfig"
#define COMMIT_MARKER "/var/tmp/initial_in_commit"
+#define QUEUE_MARKER "/var/tmp/last_in_queue"
enum {
SUCCESS = 1 << 0,
@@ -77,6 +78,7 @@ int main(int argc, char* argv[])
int ex_index;
int init_timeout = 0;
+ int last = 0;
debug_print("Connecting to vyos-configd ...\n");
zmq_connect(requester, SOCKET_PATH);
@@ -101,10 +103,16 @@ int main(int argc, char* argv[])
return ret;
}
+ if (access(QUEUE_MARKER, F_OK) != -1) {
+ last = 1;
+ remove(QUEUE_MARKER);
+ }
+
char error_code[1];
debug_print("Sending node data ...\n");
- char *string_node_data_msg = mkjson(MKJSON_OBJ, 2,
+ char *string_node_data_msg = mkjson(MKJSON_OBJ, 3,
MKJSON_STRING, "type", "node",
+ MKJSON_BOOL, "last", last,
MKJSON_STRING, "data", &string_node_data[0]);
zmq_send(requester, string_node_data_msg, strlen(string_node_data_msg), 0);
diff --git a/src/system/vyos-event-handler.py b/src/system/vyos-event-handler.py
index 74112ec91..dd2793046 100755
--- a/src/system/vyos-event-handler.py
+++ b/src/system/vyos-event-handler.py
@@ -153,7 +153,12 @@ if __name__ == '__main__':
continue
for entry in data:
message = entry['MESSAGE']
- pid = entry['_PID']
+ pid = -1
+ try:
+ pid = entry['_PID']
+ except Exception as ex:
+ journal.send(f'Unable to extract PID from message entry: {entry}', SYSLOG_IDENTIFIER=my_name)
+ continue
# Skip empty messages and messages from this process
if message and pid != my_pid:
try: