summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint-with-ruff.yml14
-rw-r--r--Makefile2
-rw-r--r--data/templates/container/containers.conf.j28
-rwxr-xr-xdata/templates/firewall/nftables.j214
-rw-r--r--data/templates/frr/rpki.frr.j228
-rw-r--r--interface-definitions/container.xml.in43
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i49
-rw-r--r--interface-definitions/include/rpki/protocol-common-config.xml.i87
-rw-r--r--interface-definitions/include/version/container-version.xml.i2
-rw-r--r--interface-definitions/include/version/firewall-version.xml.i2
-rw-r--r--interface-definitions/protocols_rpki.xml.in86
-rw-r--r--interface-definitions/vrf.xml.in9
-rw-r--r--op-mode-definitions/container.xml.in7
-rw-r--r--op-mode-definitions/include/rpki/vrf.xml.i11
-rw-r--r--op-mode-definitions/rpki.xml.in57
-rw-r--r--python/vyos/configsession.py6
-rw-r--r--python/vyos/frrender.py17
-rw-r--r--python/vyos/ifconfig/wireguard.py2
-rwxr-xr-xpython/vyos/template.py23
-rw-r--r--schema/op-mode-definition.rnc24
-rw-r--r--schema/op-mode-definition.rng35
-rwxr-xr-xscripts/build-command-op-templates27
-rw-r--r--smoketest/config-tests/firewall-bridged-global-options21
-rw-r--r--smoketest/configs/firewall-bridged-global-options60
-rwxr-xr-xsmoketest/scripts/cli/test_container.py10
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py8
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py5
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rpki.py187
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py18
-rwxr-xr-xsrc/conf_mode/container.py3
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py8
-rwxr-xr-xsrc/conf_mode/protocols_rpki.py17
-rwxr-xr-xsrc/conf_mode/vrf.py7
-rwxr-xr-xsrc/helpers/reset_section.py124
-rw-r--r--src/migration-scripts/container/2-to-331
-rw-r--r--src/migration-scripts/firewall/18-to-1935
-rw-r--r--src/migration-scripts/quagga/8-to-918
-rwxr-xr-xsrc/op_mode/container.py42
-rwxr-xr-xsrc/services/vyos-configd14
-rw-r--r--src/shim/vyshim.c41
-rw-r--r--src/tests/test_template.py4
41 files changed, 923 insertions, 283 deletions
diff --git a/.github/workflows/lint-with-ruff.yml b/.github/workflows/lint-with-ruff.yml
deleted file mode 100644
index 00cc9ca1b..000000000
--- a/.github/workflows/lint-with-ruff.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-name: Lint py code with ruff
-on:
- pull_request_target:
- branches:
- - current
-
-permissions:
- pull-requests: write
- contents: read
-
-jobs:
- ruff-lint:
- uses: vyos/.github/.github/workflows/lint-with-ruff.yml@current
- secrets: inherit
diff --git a/Makefile b/Makefile
index c397afc6e..d3248c8d2 100644
--- a/Makefile
+++ b/Makefile
@@ -100,7 +100,7 @@ clean:
.PHONY: test
test: generate-configd-include-json
- set -e; python3 -m compileall -q -x '/vmware-tools/scripts/' -x '/ppp/' .
+ set -e; python3 -m compileall -q -x '/vmware-tools/scripts/' .
PYTHONPATH=python/ python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators,src/tests --verbose
.PHONY: check_migration_scripts_executable
diff --git a/data/templates/container/containers.conf.j2 b/data/templates/container/containers.conf.j2
index 65436801e..414c3e849 100644
--- a/data/templates/container/containers.conf.j2
+++ b/data/templates/container/containers.conf.j2
@@ -170,14 +170,6 @@ default_sysctls = [
#
#label = true
-# Logging driver for the container. Available options: k8s-file and journald.
-#
-{% if log_driver is vyos_defined %}
-log_driver = "{{ log_driver }}"
-{% else %}
-#log_driver = "k8s-file"
-{% endif %}
-
# Maximum size allowed for the container log file. Negative numbers indicate
# that no size limit is imposed. If positive, it must be >= 8192 to match or
# exceed conmon's read buffer. The file is truncated and re-opened so the
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index bf051bb57..39ef72059 100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -410,15 +410,11 @@ table bridge vyos_filter {
{% for prior, conf in bridge.output.items() %}
chain VYOS_OUTPUT_{{ prior }} {
type filter hook output priority {{ prior }}; policy accept;
-{% if global_options.apply_to_bridged_traffic is vyos_defined %}
-{% if 'invalid_connections' in global_options.apply_to_bridged_traffic %}
- ct state invalid udp sport 67 udp dport 68 counter accept
- ct state invalid ether type arp counter accept
- ct state invalid ether type 8021q counter accept
- ct state invalid ether type 8021ad counter accept
- ct state invalid ether type 0x8863 counter accept
- ct state invalid ether type 0x8864 counter accept
- ct state invalid ether type 0x0842 counter accept
+{% if global_options.apply_to_bridged_traffic.accept_invalid is vyos_defined %}
+{% if 'ethernet_type' in global_options.apply_to_bridged_traffic.accept_invalid %}
+{% for ether_type in global_options.apply_to_bridged_traffic.accept_invalid.ethernet_type %}
+ {{ ether_type | nft_accept_invalid() }}
+{% endfor %}
{% endif %}
{% endif %}
{% if global_options.state_policy is vyos_defined %}
diff --git a/data/templates/frr/rpki.frr.j2 b/data/templates/frr/rpki.frr.j2
index edf0ccaa2..e35f99766 100644
--- a/data/templates/frr/rpki.frr.j2
+++ b/data/templates/frr/rpki.frr.j2
@@ -1,8 +1,8 @@
-!
+{% macro rpki_config(rpki) %}
{# as FRR does not support deleting the entire rpki section we leave it in place even when it's empty #}
rpki
-{% if cache is vyos_defined %}
-{% for peer, peer_config in cache.items() %}
+{% if rpki.cache is vyos_defined %}
+{% for peer, peer_config in rpki.cache.items() %}
{# port is mandatory and preference uses a default value #}
{% if peer_config.ssh.username is vyos_defined %}
rpki cache ssh {{ peer | replace('_', '-') }} {{ peer_config.port }} {{ peer_config.ssh.username }} {{ peer_config.ssh.private_key_file }} {{ peer_config.ssh.public_key_file }}{{ ' source ' ~ peer_config.source_address if peer_config.source_address is vyos_defined }} preference {{ peer_config.preference }}
@@ -11,14 +11,24 @@ rpki
{% endif %}
{% endfor %}
{% endif %}
-{% if expire_interval is vyos_defined %}
- rpki expire_interval {{ expire_interval }}
+{% if rpki.expire_interval is vyos_defined %}
+ rpki expire_interval {{ rpki.expire_interval }}
{% endif %}
-{% if polling_period is vyos_defined %}
- rpki polling_period {{ polling_period }}
+{% if rpki.polling_period is vyos_defined %}
+ rpki polling_period {{ rpki.polling_period }}
{% endif %}
-{% if retry_interval is vyos_defined %}
- rpki retry_interval {{ retry_interval }}
+{% if rpki.retry_interval is vyos_defined %}
+ rpki retry_interval {{ rpki.retry_interval }}
{% endif %}
exit
+{# j2lint: disable=jinja-statements-delimeter #}
+{%- endmacro -%}
+!
+{% if rpki.vrf is vyos_defined %}
+vrf {{ rpki.vrf }}
+ {{ rpki_config(rpki) | indent(width=1) }}
+exit-vrf
+{% else %}
+{{ rpki_config(rpki) }}
+{% endif %}
!
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 434bf7528..f20fd7690 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -532,6 +532,30 @@
</leafNode>
</children>
</tagNode>
+ <leafNode name="log-driver">
+ <properties>
+ <help>Configure container log driver</help>
+ <completionHelp>
+ <list>k8s-file journald none</list>
+ </completionHelp>
+ <valueHelp>
+ <format>k8s-file</format>
+ <description>Logs to plain-text file</description>
+ </valueHelp>
+ <valueHelp>
+ <format>journald</format>
+ <description>Logs to systemd's journal</description>
+ </valueHelp>
+ <valueHelp>
+ <format>none</format>
+ <description>Disable logging for the container</description>
+ </valueHelp>
+ <constraint>
+ <regex>(k8s-file|journald|none)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>journald</defaultValue>
+ </leafNode>
</children>
</tagNode>
<tagNode name="network">
@@ -627,25 +651,6 @@
</node>
</children>
</tagNode>
- <leafNode name="log-driver">
- <properties>
- <help>Configure container log driver</help>
- <completionHelp>
- <list>k8s-file journald</list>
- </completionHelp>
- <valueHelp>
- <format>k8s-file</format>
- <description>Logs to plain-text json file</description>
- </valueHelp>
- <valueHelp>
- <format>journald</format>
- <description>Logs to systemd's journal</description>
- </valueHelp>
- <constraint>
- <regex>(k8s-file|journald)</regex>
- </constraint>
- </properties>
- </leafNode>
</children>
</node>
</interfaceDefinition>
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index 794da4f9d..e19f3a7c5 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -49,12 +49,53 @@
<help>Apply configured firewall rules to traffic switched by bridges</help>
</properties>
<children>
- <leafNode name="invalid-connections">
+ <node name="accept-invalid">
<properties>
- <help>Accept ARP, 802.1q, 802.1ad, DHCP, PPPoE and WoL despite being marked as invalid connections</help>
- <valueless/>
+ <help>Accept connections despite they are marked as invalid</help>
</properties>
- </leafNode>
+ <children>
+ <leafNode name="ethernet-type">
+ <properties>
+ <help>Ethernet type</help>
+ <completionHelp>
+ <list>arp dhcp pppoe 802.1q 802.1ad pppoe-discovery wol</list>
+ </completionHelp>
+ <valueHelp>
+ <format>arp</format>
+ <description>Adress Resolution Protocol (ARP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>dhcp</format>
+ <description>Dynamic Host Configuration Protocol (DHCP)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>pppoe</format>
+ <description>Point to Point over Ethernet (PPPoE) Session</description>
+ </valueHelp>
+ <valueHelp>
+ <format>pppoe-discovery</format>
+ <description>PPPoE Discovery</description>
+ </valueHelp>
+ <valueHelp>
+ <format>802.1q</format>
+ <description>Customer VLAN tag type (802.1Q)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>802.1ad</format>
+ <description>Service VLAN tag type (802.1ad)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>wol</format>
+ <description>Wake-on-LAN magic packet</description>
+ </valueHelp>
+ <constraint>
+ <regex>(arp|dhcp|pppoe|pppoe-discovery|802.1q|802.1ad|wol)</regex>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="ipv4">
<properties>
<help>Apply configured IPv4 firewall rules</help>
diff --git a/interface-definitions/include/rpki/protocol-common-config.xml.i b/interface-definitions/include/rpki/protocol-common-config.xml.i
new file mode 100644
index 000000000..0b3356604
--- /dev/null
+++ b/interface-definitions/include/rpki/protocol-common-config.xml.i
@@ -0,0 +1,87 @@
+<!-- include start from rpki/protocol-common-config.xml.i -->
+<tagNode name="cache">
+ <properties>
+ <help>RPKI cache server address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IP address of RPKI server</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of RPKI server</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>Fully qualified domain name of RPKI server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ <validator name="fqdn"/>
+ </constraint>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="preference">
+ <properties>
+ <help>Preference of the cache server</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Preference of the cache server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ #include <include/source-address-ipv4.xml.i>
+ <node name="ssh">
+ <properties>
+ <help>RPKI SSH connection settings</help>
+ </properties>
+ <children>
+ #include <include/pki/openssh-key.xml.i>
+ #include <include/generic-username.xml.i>
+ </children>
+ </node>
+ </children>
+</tagNode>
+<leafNode name="expire-interval">
+ <properties>
+ <help>Interval to wait before expiring the cache</help>
+ <valueHelp>
+ <format>u32:600-172800</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 600-172800"/>
+ </constraint>
+ </properties>
+ <defaultValue>7200</defaultValue>
+</leafNode>
+<leafNode name="polling-period">
+ <properties>
+ <help>Cache polling interval</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ <defaultValue>300</defaultValue>
+</leafNode>
+<leafNode name="retry-interval">
+ <properties>
+ <help>Retry interval to connect to the cache server</help>
+ <valueHelp>
+ <format>u32:1-7200</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-7200"/>
+ </constraint>
+ </properties>
+ <defaultValue>600</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/container-version.xml.i b/interface-definitions/include/version/container-version.xml.i
index ed6e942cd..046bacfdc 100644
--- a/interface-definitions/include/version/container-version.xml.i
+++ b/interface-definitions/include/version/container-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/container-version.xml.i -->
-<syntaxVersion component='container' version='2'></syntaxVersion>
+<syntaxVersion component='container' version='3'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i
index 1a8098297..1f3b779d5 100644
--- a/interface-definitions/include/version/firewall-version.xml.i
+++ b/interface-definitions/include/version/firewall-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/firewall-version.xml.i -->
-<syntaxVersion component='firewall' version='18'></syntaxVersion>
+<syntaxVersion component='firewall' version='19'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/protocols_rpki.xml.in b/interface-definitions/protocols_rpki.xml.in
index 9e2e84717..a298cdbfd 100644
--- a/interface-definitions/protocols_rpki.xml.in
+++ b/interface-definitions/protocols_rpki.xml.in
@@ -8,91 +8,7 @@
<priority>819</priority>
</properties>
<children>
- <tagNode name="cache">
- <properties>
- <help>RPKI cache server address</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IP address of RPKI server</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address of RPKI server</description>
- </valueHelp>
- <valueHelp>
- <format>hostname</format>
- <description>Fully qualified domain name of RPKI server</description>
- </valueHelp>
- <constraint>
- <validator name="ip-address"/>
- <validator name="fqdn"/>
- </constraint>
- </properties>
- <children>
- #include <include/port-number.xml.i>
- <leafNode name="preference">
- <properties>
- <help>Preference of the cache server</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Preference of the cache server</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/source-address-ipv4.xml.i>
- <node name="ssh">
- <properties>
- <help>RPKI SSH connection settings</help>
- </properties>
- <children>
- #include <include/pki/openssh-key.xml.i>
- #include <include/generic-username.xml.i>
- </children>
- </node>
- </children>
- </tagNode>
- <leafNode name="expire-interval">
- <properties>
- <help>Interval to wait before expiring the cache</help>
- <valueHelp>
- <format>u32:600-172800</format>
- <description>Interval in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 600-172800"/>
- </constraint>
- </properties>
- <defaultValue>7200</defaultValue>
- </leafNode>
- <leafNode name="polling-period">
- <properties>
- <help>Cache polling interval</help>
- <valueHelp>
- <format>u32:1-86400</format>
- <description>Interval in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-86400"/>
- </constraint>
- </properties>
- <defaultValue>300</defaultValue>
- </leafNode>
- <leafNode name="retry-interval">
- <properties>
- <help>Retry interval to connect to the cache server</help>
- <valueHelp>
- <format>u32:1-7200</format>
- <description>Interval in seconds</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-7200"/>
- </constraint>
- </properties>
- <defaultValue>600</defaultValue>
- </leafNode>
+ #include <include/rpki/protocol-common-config.xml.i>
</children>
</node>
</children>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index a20be995a..03128cb99 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -95,6 +95,15 @@
#include <include/ospfv3/protocol-common-config.xml.i>
</children>
</node>
+ <node name="rpki" owner="${vyos_conf_scripts_dir}/protocols_rpki.py $VAR(../../@)">
+ <properties>
+ <help>Resource Public Key Infrastructure (RPKI)</help>
+ <priority>820</priority>
+ </properties>
+ <children>
+ #include <include/rpki/protocol-common-config.xml.i>
+ </children>
+ </node>
<node name="static" owner="${vyos_conf_scripts_dir}/protocols_static.py $VAR(../../@)">
<properties>
<help>Static Routing</help>
diff --git a/op-mode-definitions/container.xml.in b/op-mode-definitions/container.xml.in
index df28a792c..70ab4b6e5 100644
--- a/op-mode-definitions/container.xml.in
+++ b/op-mode-definitions/container.xml.in
@@ -89,7 +89,7 @@
<path>container name</path>
</completionHelp>
</properties>
- <command>podman logs --follow --names "$4"</command>
+ <command>${vyos_op_scripts_dir}/container.py show_log --follow --name "$4"</command>
</tagNode>
</children>
</node>
@@ -132,8 +132,7 @@
<path>container name</path>
</completionHelp>
</properties>
- <!-- no admin check -->
- <command>podman logs --names "$4"</command>
+ <command>${vyos_op_scripts_dir}/container.py show_log --name "$4"</command>
</tagNode>
<node name="network">
<properties>
@@ -162,7 +161,7 @@
<path>container name</path>
</completionHelp>
</properties>
- <command>podman logs --names "$4"</command>
+ <command>${vyos_op_scripts_dir}/container.py show_log --name "$4"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/include/rpki/vrf.xml.i b/op-mode-definitions/include/rpki/vrf.xml.i
new file mode 100644
index 000000000..5b6518fee
--- /dev/null
+++ b/op-mode-definitions/include/rpki/vrf.xml.i
@@ -0,0 +1,11 @@
+<!-- include start from rpki/vrf.xml.i -->
+<tagNode name="vrf">
+ <properties>
+ <help>Virtual Routing and Forwarding (VRF)</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</tagNode>
+<!-- include end -->
diff --git a/op-mode-definitions/rpki.xml.in b/op-mode-definitions/rpki.xml.in
index 9e0f83e20..4753cfb93 100644
--- a/op-mode-definitions/rpki.xml.in
+++ b/op-mode-definitions/rpki.xml.in
@@ -15,19 +15,28 @@
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/rpki/vrf.xml.i>
+ </children>
</tagNode>
- <leafNode name="cache-connection">
+ <node name="cache-connection">
<properties>
<help>Show RPKI cache connections</help>
</properties>
- <command>vtysh -c "show rpki cache-connection"</command>
- </leafNode>
- <leafNode name="cache-server">
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/rpki/vrf.xml.i>
+ </children>
+ </node>
+ <node name="cache-server">
<properties>
<help>Show RPKI cache servers information</help>
</properties>
- <command>vtysh -c "show rpki cache-server"</command>
- </leafNode>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/rpki/vrf.xml.i>
+ </children>
+ </node>
<tagNode name="prefix">
<properties>
<help>Lookup IP prefix and optionally ASN in prefix table</help>
@@ -45,27 +54,53 @@
</completionHelp>
</properties>
<command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $(echo $@ | sed -e "s/as-number //g")</command>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Virtual Routing and Forwarding (VRF)</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $(echo $@ | sed -e "s/as-number //g")</command>
+ </tagNode>
+ </children>
</tagNode>
+ #include <include/rpki/vrf.xml.i>
</children>
</tagNode>
- <leafNode name="prefix-table">
+ <node name="prefix-table">
<properties>
<help>Show RPKI-validated prefixes</help>
</properties>
- <command>vtysh -c "show rpki prefix-table"</command>
- </leafNode>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <children>
+ #include <include/rpki/vrf.xml.i>
+ </children>
+ </node>
</children>
</node>
</children>
</node>
<node name="reset">
<children>
- <leafNode name="rpki">
+ <node name="rpki">
<properties>
<help>Reset RPKI</help>
</properties>
<command>vtysh -c "rpki reset"</command>
- </leafNode>
+ <children>
+ <tagNode name="vrf">
+ <properties>
+ <help>Reset RPKI in VRF</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>vtysh -c "rpki reset vrf $4"</command>
+ </tagNode>
+ </children>
+ </node>
</children>
</node>
</interfaceDefinition>
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index f0d636b89..7af2cb333 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -146,7 +146,7 @@ class ConfigSession(object):
The write API of VyOS.
"""
- def __init__(self, session_id, app=APP):
+ def __init__(self, session_id, app=APP, shared=False):
"""
Creates a new config session.
@@ -187,7 +187,11 @@ class ConfigSession(object):
else:
self._vyconf_session = None
+ self.shared = shared
+
def __del__(self):
+ if self.shared:
+ return
if self._vyconf_session is None:
try:
output = (
diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py
index 73d6dd5f0..d9e409cb4 100644
--- a/python/vyos/frrender.py
+++ b/python/vyos/frrender.py
@@ -543,6 +543,21 @@ def get_frrender_dict(conf, argv=None) -> dict:
elif conf.exists_effective(ospfv3_vrf_path):
vrf['name'][vrf_name]['protocols'].update({'ospfv3' : {'deleted' : ''}})
+ # We need to check the CLI if the RPKI node is present and thus load in all the default
+ # values present on the CLI - that's why we have if conf.exists()
+ rpki_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'rpki']
+ if 'rpki' in vrf_config.get('protocols', []):
+ rpki = conf.get_config_dict(rpki_vrf_path, key_mangling=('-', '_'), get_first_key=True,
+ with_pki=True, with_recursive_defaults=True)
+ rpki_ssh_key_base = '/run/frr/id_rpki'
+ for cache, cache_config in rpki.get('cache',{}).items():
+ if 'ssh' in cache_config:
+ cache_config['ssh']['public_key_file'] = f'{rpki_ssh_key_base}_{cache}.pub'
+ cache_config['ssh']['private_key_file'] = f'{rpki_ssh_key_base}_{cache}'
+ vrf['name'][vrf_name]['protocols'].update({'rpki' : rpki})
+ elif conf.exists_effective(rpki_vrf_path):
+ vrf['name'][vrf_name]['protocols'].update({'rpki' : {'deleted' : ''}})
+
# We need to check the CLI if the static node is present and thus load in all the default
# values present on the CLI - that's why we have if conf.exists()
static_vrf_path = ['vrf', 'name', vrf_name, 'protocols', 'static']
@@ -675,7 +690,7 @@ class FRRender:
output += render_to_string('frr/ripngd.frr.j2', config_dict['ripng'])
output += '\n'
if 'rpki' in config_dict and 'deleted' not in config_dict['rpki']:
- output += render_to_string('frr/rpki.frr.j2', config_dict['rpki'])
+ output += render_to_string('frr/rpki.frr.j2', {'rpki': config_dict['rpki']})
output += '\n'
if 'segment_routing' in config_dict and 'deleted' not in config_dict['segment_routing']:
output += render_to_string('frr/zebra.segment_routing.frr.j2', config_dict['segment_routing'])
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 3a28723b3..6b5e52412 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -52,7 +52,7 @@ class WireGuardOperational(Operational):
'private_key': None if private_key == '(none)' else private_key,
'public_key': None if public_key == '(none)' else public_key,
'listen_port': int(listen_port),
- 'fw_mark': None if fw_mark == 'off' else int(fw_mark),
+ 'fw_mark': None if fw_mark == 'off' else int(fw_mark, 16),
'peers': {},
}
else:
diff --git a/python/vyos/template.py b/python/vyos/template.py
index bf7928914..bf2f13183 100755
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -674,6 +674,29 @@ def nft_nested_group(out_list, includes, groups, key):
add_includes(name)
return out_list
+@register_filter('nft_accept_invalid')
+def nft_accept_invalid(ether_type):
+ ether_type_mapping = {
+ 'dhcp': 'udp sport 67 udp dport 68',
+ 'arp': 'arp',
+ 'pppoe-discovery': '0x8863',
+ 'pppoe': '0x8864',
+ '802.1q': '8021q',
+ '802.1ad': '8021ad',
+ 'wol': '0x0842',
+ }
+ if ether_type not in ether_type_mapping:
+ raise RuntimeError(f'Ethernet type "{ether_type}" not found in ' \
+ 'available ethernet types!')
+ out = 'ct state invalid '
+
+ if ether_type != 'dhcp':
+ out += 'ether type '
+
+ out += f'{ether_type_mapping[ether_type]} counter accept'
+
+ return out
+
@register_filter('nat_rule')
def nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
from vyos.nat import parse_nat_rule
diff --git a/schema/op-mode-definition.rnc b/schema/op-mode-definition.rnc
index ad41700b9..46430daa4 100644
--- a/schema/op-mode-definition.rnc
+++ b/schema/op-mode-definition.rnc
@@ -1,6 +1,6 @@
# interface_definition.rnc: VyConf reference tree XML grammar
#
-# Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net>
+# Copyright (C) 2014-2025 VyOS maintainers and contributors <maintainers@vyos.net>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -37,13 +37,27 @@ node = element node
}
# Tag nodes are containers for nodes without predefined names, like network interfaces
-# or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker")
-# Tag nodes may contain node and leafNode elements, and also nameConstraint tags
-# They must not contain other tag nodes
+# or user names (e.g. "show interfaces ethernet ethX").
+# Operational mode tag nodes can be parents to other tag nodes,
+# like in "ping <host> count <packets>".
+#
+# Some commands can be called either with or without arguments,
+# like "show interfaces ethernet eth0" (show info for eth0 only)
+# or "show interfaces ethernet" (show info about all ethernet interfaces).
+# That case is handled using the <standalone> element
tagNode = element tagNode
{
nodeNameAttr,
- (properties? & children? & command?)
+ (properties? & standalone? & children? & command?)
+}
+
+# The <standalone> element is only used inside tag nodes
+# to define their behavior when they are called without arguments
+# It can provide a help string and a command.
+# Everything else is handled in the <tagNode> itself.
+standalone = element standalone
+{
+ help & command
}
# Leaf nodes are terminal configuration nodes that can't have children,
diff --git a/schema/op-mode-definition.rng b/schema/op-mode-definition.rng
index a255aeb73..bfd5cb50a 100644
--- a/schema/op-mode-definition.rng
+++ b/schema/op-mode-definition.rng
@@ -3,7 +3,7 @@
<!--
interface_definition.rnc: VyConf reference tree XML grammar
- Copyright (C) 2014. 2017 VyOS maintainers and contributors <maintainers@vyos.net>
+ Copyright (C) 2014-2025 VyOS maintainers and contributors <maintainers@vyos.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
@@ -59,9 +59,14 @@
</define>
<!--
Tag nodes are containers for nodes without predefined names, like network interfaces
- or user names (e.g. "interfaces ethernet eth0" or "user jrandomhacker")
- Tag nodes may contain node and leafNode elements, and also nameConstraint tags
- They must not contain other tag nodes
+ or user names (e.g. "show interfaces ethernet ethX").
+ Operational mode tag nodes can be parents to other tag nodes,
+ like in "ping <host> count <packets>".
+
+ Some commands can be called either with or without arguments,
+ like "show interfaces ethernet eth0" (show info for eth0 only)
+ or "show interfaces ethernet" (show info about all ethernet interfaces).
+ That case is handled using the <standalone> element
-->
<define name="tagNode">
<element name="tagNode">
@@ -71,6 +76,9 @@
<ref name="properties"/>
</optional>
<optional>
+ <ref name="standalone"/>
+ </optional>
+ <optional>
<ref name="children"/>
</optional>
<optional>
@@ -80,6 +88,20 @@
</element>
</define>
<!--
+ The <standalone> element is only used inside tag nodes
+ to define their behavior when they are called without arguments
+ It can provide a help string and a command.
+ Everything else is handled in the <tagNode> itself.
+ -->
+ <define name="standalone">
+ <element name="standalone">
+ <interleave>
+ <ref name="help"/>
+ <ref name="command"/>
+ </interleave>
+ </element>
+ </define>
+ <!--
Leaf nodes are terminal configuration nodes that can't have children,
but can have values.
-->
@@ -139,10 +161,11 @@
<!--
completionHelp tags contain information about allowed values of a node that is used for generating
tab completion in the CLI frontend and drop-down lists in GUI frontends
- It is only meaninful for leaf nodes
+ It is only meaningful for leaf nodes
Allowed values can be given as a fixed list of values (e.g. <list>foo bar baz</list>),
as a configuration path (e.g. <path>interfaces ethernet</path>),
- or as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script>
+ as a path to a script file that generates the list (e.g. <script>/usr/lib/foo/list-things</script>,
+ or to enable built-in image path completion (<imagePath/>).
-->
<define name="completionHelp">
<element name="completionHelp">
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
index 0bb62113e..9eef25a2f 100755
--- a/scripts/build-command-op-templates
+++ b/scripts/build-command-op-templates
@@ -124,6 +124,26 @@ def get_properties(p):
return props
+def get_standalone(s):
+ standalone = {}
+
+ if s is None:
+ return {}
+
+ # Get the help string
+ try:
+ standalone["help"] = s.find("help").text
+ except:
+ standalone["help"] = "No help available"
+
+ # Get the command -- it's required by the schema
+ try:
+ standalone["command"] = s.find("command")
+ except:
+ raise AssertionError("Found a <standalone> node without <command>")
+
+ return standalone
+
def make_node_def(props, command):
# XXX: replace with a template processor if it grows
@@ -150,6 +170,7 @@ def process_node(n, tmpl_dir):
my_tmpl_dir = copy.copy(tmpl_dir)
props_elem = n.find("properties")
+ standalone_elem = n.find("standalone")
children = n.find("children")
command = n.find("command")
name = n.get("name")
@@ -163,6 +184,7 @@ def process_node(n, tmpl_dir):
os.makedirs(make_path(my_tmpl_dir), exist_ok=True)
props = get_properties(props_elem)
+ standalone = get_standalone(standalone_elem)
nodedef_path = os.path.join(make_path(my_tmpl_dir), "node.def")
if node_type == "node":
@@ -189,7 +211,10 @@ def process_node(n, tmpl_dir):
# does not exist at all.
if not os.path.exists(nodedef_path) or os.path.getsize(nodedef_path) == 0:
with open(nodedef_path, "w") as f:
- f.write('help: {0}\n'.format(props['help']))
+ if standalone:
+ f.write(make_node_def(standalone, standalone["command"]))
+ else:
+ f.write('help: {0}\n'.format(props['help']))
# Create the inner node.tag part
my_tmpl_dir.append("node.tag")
diff --git a/smoketest/config-tests/firewall-bridged-global-options b/smoketest/config-tests/firewall-bridged-global-options
new file mode 100644
index 000000000..1d960d6c1
--- /dev/null
+++ b/smoketest/config-tests/firewall-bridged-global-options
@@ -0,0 +1,21 @@
+set firewall bridge prerouting filter rule 10 action 'accept'
+set firewall bridge prerouting filter rule 10 ethernet-type 'arp'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type 'dhcp'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type 'arp'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type 'pppoe-discovery'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type 'pppoe'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type '802.1q'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type '802.1ad'
+set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type 'wol'
+set firewall global-options state-policy established action 'accept'
+set firewall global-options state-policy invalid action 'drop'
+set firewall global-options state-policy related action 'accept'
+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 system console device ttyS0 speed '115200'
+set system domain-name 'vyos-ci-test.net'
+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 ''
diff --git a/smoketest/configs/firewall-bridged-global-options b/smoketest/configs/firewall-bridged-global-options
new file mode 100644
index 000000000..a7e1428d8
--- /dev/null
+++ b/smoketest/configs/firewall-bridged-global-options
@@ -0,0 +1,60 @@
+firewall {
+ bridge {
+ prerouting {
+ filter {
+ rule 10 {
+ action "accept"
+ ethernet-type "arp"
+ }
+ }
+ }
+ }
+ global-options {
+ apply-to-bridged-traffic {
+ invalid-connections {
+ }
+ }
+ state-policy {
+ established {
+ action "accept"
+ }
+ invalid {
+ action "drop"
+ }
+ related {
+ action "accept"
+ }
+ }
+ }
+}
+interfaces {
+ ethernet eth0 {
+ duplex "auto"
+ speed "auto"
+ }
+ ethernet eth1 {
+ duplex auto
+ speed auto
+ }
+}
+system {
+ console {
+ device ttyS0 {
+ speed 115200
+ }
+ }
+ domain-name vyos-ci-test.net
+ host-name vyos
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0
+ plaintext-password ""
+ }
+ }
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@6:broadcast-relay@1:cluster@2:config-management@1:conntrack@6:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@11:dhcpv6-server@6:dns-dynamic@4:dns-forwarding@4:firewall@18:flow-accounting@2:https@7:ids@2:interfaces@33:ipoe-server@4:ipsec@13:isis@3:l2tp@9:lldp@3:mdns@1:monitoring@2:nat@8:nat66@3:nhrp@1:ntp@3:openconnect@3:openvpn@4:ospf@2:pim@1:policy@9:pppoe-server@11:pptp@5:qos@3:quagga@12:reverse-proxy@3:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@29:vpp@1:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@4:webproxy@2"
+// Release version: 2025.06.17-0020-rolling
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index daad3a909..892d7ae0b 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -95,7 +95,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
'4096',
]
)
-
+ self.cli_set(base_path + ['name', cont_name, 'log-driver', 'journald'])
# commit changes
self.cli_commit()
@@ -110,13 +110,9 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
tmp = cmd(f'sudo podman exec -it {cont_name} sysctl kernel.msgmax')
self.assertEqual(tmp, 'kernel.msgmax = 4096')
- def test_log_driver(self):
- self.cli_set(base_path + ['log-driver', 'journald'])
-
- self.cli_commit()
+ l = cmd_to_json(f'sudo podman container inspect {cont_name}')
+ self.assertEqual(l['HostConfig']['LogConfig']['Type'], 'journald')
- tmp = cmd('podman info --format "{{ .Host.LogDriver }}"')
- self.assertEqual(tmp, 'journald')
def test_name_server(self):
cont_name = 'dns-test'
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index 2d850dfdf..455c704d0 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -728,7 +728,13 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'group', 'ipv6-address-group', 'AGV6', 'address', '2001:db1::1'])
self.cli_set(['firewall', 'global-options', 'state-policy', 'established', 'action', 'accept'])
self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'ipv4'])
- self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'invalid-connections'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', 'dhcp'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', 'arp'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', 'pppoe'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', 'pppoe-discovery'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', '802.1q'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', '802.1ad'])
+ self.cli_set(['firewall', 'global-options', 'apply-to-bridged-traffic', 'accept-invalid', 'ethernet-type', 'wol'])
self.cli_set(['firewall', 'bridge', 'name', name, 'default-action', 'accept'])
self.cli_set(['firewall', 'bridge', 'name', name, 'default-log'])
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index e087b8735..42c5ba848 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -826,7 +826,6 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
gw_subnet = "192.168.0.1"
self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vtun_if])
- self.cli_set(path + ['device-type', 'tap'])
self.cli_set(path + ['encryption', 'data-ciphers', 'aes192'])
self.cli_set(path + ['hash', auth_hash])
self.cli_set(path + ['mode', 'server'])
@@ -840,6 +839,10 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_set(path + ['tls', 'certificate', 'ovpn_test'])
self.cli_set(path + ['tls', 'dh-params', 'ovpn_test'])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(path + ['device-type', 'tap'])
self.cli_commit()
config_file = f'/run/openvpn/{vtun_if}.conf'
diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py
index 0addf7fee..5ea257088 100755
--- a/smoketest/scripts/cli/test_protocols_rpki.py
+++ b/smoketest/scripts/cli/test_protocols_rpki.py
@@ -25,6 +25,11 @@ from vyos.utils.file import read_file
from vyos.utils.process import process_named_running
base_path = ['protocols', 'rpki']
+base_frr_config_args = {'string': 'rpki', 'endsection': '^exit'}
+vrf = 'blue'
+vrf_path = ['vrf', 'name', vrf]
+vrf_frr_config_args = {'string': f'vrf {vrf}', 'endsection':'^exit-vrf',
+ 'substring': ' rpki', 'endsubsection': '^ exit'}
rpki_key_name = 'rpki-smoketest'
rpki_key_type = 'ssh-rsa'
@@ -112,14 +117,19 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
+ cls.cli_delete(cls, vrf_path)
# Enable CSTORE guard time required by FRR related tests
cls._commit_guard_time = CSTORE_GUARD_TIME
def tearDown(self):
self.cli_delete(base_path)
+ self.cli_delete(vrf_path)
self.cli_commit()
- frrconfig = self.getFRRconfig('rpki', endsection='^exit')
+ frrconfig = self.getFRRconfig(**base_frr_config_args)
+ self.assertNotIn(f'rpki', frrconfig)
+
+ frrconfig = self.getFRRconfig(**vrf_frr_config_args)
self.assertNotIn(f'rpki', frrconfig)
# check process health and continuity
@@ -144,27 +154,33 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
},
}
- self.cli_set(base_path + ['expire-interval', expire_interval])
- self.cli_set(base_path + ['polling-period', polling_period])
- self.cli_set(base_path + ['retry-interval', retry_interval])
+ for test_set in [ {'path': base_path, 'frrargs': base_frr_config_args},
+ {'path': vrf_path + base_path, 'frrargs': vrf_frr_config_args} ]:
- for peer, peer_config in cache.items():
- self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']])
- self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']])
+ if 'vrf' in test_set['path']:
+ self.cli_set(vrf_path + ['table', '1000'])
- # commit changes
- self.cli_commit()
+ self.cli_set(test_set['path'] + ['expire-interval', expire_interval])
+ self.cli_set(test_set['path'] + ['polling-period', polling_period])
+ self.cli_set(test_set['path'] + ['retry-interval', retry_interval])
+
+ for peer, peer_config in cache.items():
+ self.cli_set(test_set['path'] + ['cache', peer, 'port', peer_config['port']])
+ self.cli_set(test_set['path'] + ['cache', peer, 'preference', peer_config['preference']])
+
+ # commit changes
+ self.cli_commit()
- # Verify FRR configuration
- frrconfig = self.getFRRconfig('rpki', endsection='^exit')
- self.assertIn(f'rpki expire_interval {expire_interval}', frrconfig)
- self.assertIn(f'rpki polling_period {polling_period}', frrconfig)
- self.assertIn(f'rpki retry_interval {retry_interval}', frrconfig)
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig(**test_set['frrargs'])
+ self.assertIn(f'rpki expire_interval {expire_interval}', frrconfig)
+ self.assertIn(f'rpki polling_period {polling_period}', frrconfig)
+ self.assertIn(f'rpki retry_interval {retry_interval}', frrconfig)
- for peer, peer_config in cache.items():
- port = peer_config['port']
- preference = peer_config['preference']
- self.assertIn(f'rpki cache tcp {peer} {port} preference {preference}', frrconfig)
+ for peer, peer_config in cache.items():
+ port = peer_config['port']
+ preference = peer_config['preference']
+ self.assertIn(f'rpki cache tcp {peer} {port} preference {preference}', frrconfig)
def test_rpki_ssh(self):
polling = '7200'
@@ -185,28 +201,34 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n','')])
self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type])
- for cache_name, cache_config in cache.items():
- self.cli_set(base_path + ['cache', cache_name, 'port', cache_config['port']])
- self.cli_set(base_path + ['cache', cache_name, 'preference', cache_config['preference']])
- self.cli_set(base_path + ['cache', cache_name, 'ssh', 'username', cache_config['username']])
- self.cli_set(base_path + ['cache', cache_name, 'ssh', 'key', rpki_key_name])
+ for test_set in [ {'path': base_path, 'frrargs': base_frr_config_args},
+ {'path': vrf_path + base_path, 'frrargs': vrf_frr_config_args} ]:
- # commit changes
- self.cli_commit()
+ if 'vrf' in test_set['path']:
+ self.cli_set(vrf_path + ['table', '1000'])
+
+ for cache_name, cache_config in cache.items():
+ self.cli_set(test_set['path'] + ['cache', cache_name, 'port', cache_config['port']])
+ self.cli_set(test_set['path'] + ['cache', cache_name, 'preference', cache_config['preference']])
+ self.cli_set(test_set['path'] + ['cache', cache_name, 'ssh', 'username', cache_config['username']])
+ self.cli_set(test_set['path'] + ['cache', cache_name, 'ssh', 'key', rpki_key_name])
+
+ # commit changes
+ self.cli_commit()
- # Verify FRR configuration
- frrconfig = self.getFRRconfig('rpki', endsection='^exit')
- for cache_name, cache_config in cache.items():
- port = cache_config['port']
- preference = cache_config['preference']
- username = cache_config['username']
- self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig(**test_set['frrargs'])
+ for cache_name, cache_config in cache.items():
+ port = cache_config['port']
+ preference = cache_config['preference']
+ username = cache_config['username']
+ self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
- # Verify content of SSH keys
- tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
- self.assertIn(rpki_ssh_key.replace('\n',''), tmp)
- tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub')
- self.assertIn(rpki_ssh_pub.replace('\n',''), tmp)
+ # Verify content of SSH keys
+ tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
+ self.assertIn(rpki_ssh_key.replace('\n',''), tmp)
+ tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub')
+ self.assertIn(rpki_ssh_pub.replace('\n',''), tmp)
# Change OpenSSH key and verify it was properly written to filesystem
self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key_replacement.replace('\n','')])
@@ -214,17 +236,21 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
# commit changes
self.cli_commit()
- for cache_name, cache_config in cache.items():
- port = cache_config['port']
- preference = cache_config['preference']
- username = cache_config['username']
- self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
+ for test_set in [ {'path': base_path, 'frrargs': base_frr_config_args},
+ {'path': vrf_path + base_path, 'frrargs': vrf_frr_config_args} ]:
- # Verify content of SSH keys
- tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
- self.assertIn(rpki_ssh_key_replacement.replace('\n',''), tmp)
- tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub')
- self.assertIn(rpki_ssh_pub_replacement.replace('\n',''), tmp)
+ frrconfig = self.getFRRconfig(**test_set['frrargs'])
+ for cache_name, cache_config in cache.items():
+ port = cache_config['port']
+ preference = cache_config['preference']
+ username = cache_config['username']
+ self.assertIn(f'rpki cache ssh {cache_name} {port} {username} /run/frr/id_rpki_{cache_name} /run/frr/id_rpki_{cache_name}.pub preference {preference}', frrconfig)
+
+ # Verify content of SSH keys
+ tmp = read_file(f'/run/frr/id_rpki_{cache_name}')
+ self.assertIn(rpki_ssh_key_replacement.replace('\n',''), tmp)
+ tmp = read_file(f'/run/frr/id_rpki_{cache_name}.pub')
+ self.assertIn(rpki_ssh_pub_replacement.replace('\n',''), tmp)
self.cli_delete(['pki', 'openssh'])
@@ -240,13 +266,19 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
},
}
- for peer, peer_config in cache.items():
- self.cli_set(base_path + ['cache', peer, 'port', peer_config['port']])
- self.cli_set(base_path + ['cache', peer, 'preference', peer_config['preference']])
+ for test_set in [ {'path': base_path, 'frrargs': base_frr_config_args},
+ {'path': vrf_path + base_path, 'frrargs': vrf_frr_config_args} ]:
- # check validate() - preferences must be unique
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
+ if 'vrf' in test_set['path']:
+ self.cli_set(vrf_path + ['table', '1000'])
+
+ for peer, peer_config in cache.items():
+ self.cli_set(test_set['path'] + ['cache', peer, 'port', peer_config['port']])
+ self.cli_set(test_set['path'] + ['cache', peer, 'preference', peer_config['preference']])
+
+ # check validate() - preferences must be unique
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
def test_rpki_source_address(self):
peer = '192.0.2.1'
@@ -257,31 +289,38 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase):
self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', f'{source_address}/24'])
- # Configure a TCP cache server
- self.cli_set(base_path + ['cache', peer, 'port', port])
- self.cli_set(base_path + ['cache', peer, 'preference', preference])
- self.cli_set(base_path + ['cache', peer, 'source-address', source_address])
- self.cli_commit()
- # Verify FRR configuration
- frrconfig = self.getFRRconfig('rpki')
- self.assertIn(f'rpki cache tcp {peer} {port} source {source_address} preference {preference}', frrconfig)
+ for test_set in [ {'path': base_path, 'frrargs': base_frr_config_args},
+ {'path': vrf_path + base_path, 'frrargs': vrf_frr_config_args} ]:
- self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key.replace('\n', '')])
- self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n', '')])
- self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type])
+ if 'vrf' in test_set['path']:
+ self.cli_set(vrf_path + ['table', '1000'])
- # Configure a SSH cache server
- self.cli_set(base_path + ['cache', peer, 'ssh', 'username', username])
- self.cli_set(base_path + ['cache', peer, 'ssh', 'key', rpki_key_name])
- self.cli_commit()
+ # Configure a TCP cache server
+ self.cli_set(test_set['path'] + ['cache', peer, 'port', port])
+ self.cli_set(test_set['path'] + ['cache', peer, 'preference', preference])
+ self.cli_set(test_set['path'] + ['cache', peer, 'source-address', source_address])
+ self.cli_commit()
+
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig(**test_set['frrargs'])
+ self.assertIn(f'rpki cache tcp {peer} {port} source {source_address} preference {preference}', frrconfig)
+
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'private', 'key', rpki_ssh_key.replace('\n', '')])
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'key', rpki_ssh_pub.replace('\n', '')])
+ self.cli_set(['pki', 'openssh', rpki_key_name, 'public', 'type', rpki_key_type])
+
+ # Configure a SSH cache server
+ self.cli_set(test_set['path'] + ['cache', peer, 'ssh', 'username', username])
+ self.cli_set(test_set['path'] + ['cache', peer, 'ssh', 'key', rpki_key_name])
+ self.cli_commit()
- # Verify FRR configuration
- frrconfig = self.getFRRconfig('rpki')
- self.assertIn(
- f'rpki cache ssh {peer} {port} {username} /run/frr/id_rpki_{peer} /run/frr/id_rpki_{peer}.pub source {source_address} preference {preference}',
- frrconfig,
- )
+ # Verify FRR configuration
+ frrconfig = self.getFRRconfig(**test_set['frrargs'])
+ self.assertIn(
+ f'rpki cache ssh {peer} {port} {username} /run/frr/id_rpki_{peer} /run/frr/id_rpki_{peer}.pub source {source_address} preference {preference}',
+ frrconfig,
+ )
if __name__ == '__main__':
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index 84e9c145d..8188e7678 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -143,5 +143,23 @@ class TestKernelModules(unittest.TestCase):
tmp = re.findall(f'{option}=3', self._config_data)
self.assertTrue(tmp)
+ def test_inotify_stackfs(self):
+ for option in ['CONFIG_INOTIFY_USER', 'CONFIG_INOTIFY_STACKFS']:
+ tmp = re.findall(f'{option}=y', self._config_data)
+ self.assertTrue(tmp)
+
+ def test_wwan(self):
+ for option in ['CONFIG_USB_NET_DRIVERS', 'CONFIG_USB_USBNET',
+ 'CONFIG_USB_NET_CDCETHER', 'CONFIG_USB_NET_HUAWEI_CDC_NCM',
+ 'CONFIG_USB_NET_CDC_MBIM', 'CONFIG_USB_NET_QMI_WWAN',
+ 'CONFIG_USB_SIERRA_NET', 'CONFIG_WWAN',
+ 'CONFIG_USB_SERIAL', 'CONFIG_USB_SERIAL_WWAN']:
+ tmp = re.findall(f'{option}=y', self._config_data)
+ self.assertTrue(tmp)
+
+ for option in ['CONFIG_WWAN_HWSIM', 'CONFIG_IOSM', 'CONFIG_MTK_T7XX']:
+ tmp = re.findall(f'{option}=m', self._config_data)
+ self.assertTrue(tmp)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index 83e6dee11..a381ace5c 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -310,6 +310,7 @@ def generate_run_arguments(name, container_config):
memory = container_config['memory']
shared_memory = container_config['shared_memory']
restart = container_config['restart']
+ log_driver = container_config['log_driver']
# Add sysctl options
sysctl_opt = ''
@@ -408,7 +409,7 @@ def generate_run_arguments(name, container_config):
name_server += f'--dns {ns}'
container_base_cmd = f'--detach --interactive --tty --replace {capabilities} {privileged} --cpus {cpu_quota} {sysctl_opt} ' \
- f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
+ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} --log-driver={log_driver} ' \
f'--name {name} {hostname} {device} {port} {name_server} {volume} {tmpfs} {env_opt} {label} {uid} {host_pid}'
entrypoint = ''
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index c14e6a599..fce07ae0a 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -111,6 +111,11 @@ def get_config(config=None):
elif interface.startswith('wlan') and interface_exists(interface):
set_dependents('wlan', conf, interface)
+ if interface.startswith('vtun'):
+ _, tmp_config = get_interface_dict(conf, ['interfaces', 'openvpn'], interface)
+ tmp = tmp_config.get('device_type') == 'tap'
+ bridge['member']['interface'][interface].update({'valid_ovpn' : tmp})
+
# delete empty dictionary keys - no need to run code paths if nothing is there to do
if 'member' in bridge:
if 'interface' in bridge['member'] and len(bridge['member']['interface']) == 0:
@@ -178,6 +183,9 @@ def verify(bridge):
if option in interface_config:
raise ConfigError('Can not use VLAN options on non VLAN aware bridge')
+ if interface.startswith('vtun') and not interface_config['valid_ovpn']:
+ raise ConfigError(error_msg + 'OpenVPN device-type must be set to "tap"')
+
if 'enable_vlan' in bridge:
if dict_search('vif.1', bridge):
raise ConfigError(f'VLAN 1 sub interface cannot be set for VLAN aware bridge {ifname}, and VLAN 1 is always the parent interface')
diff --git a/src/conf_mode/protocols_rpki.py b/src/conf_mode/protocols_rpki.py
index ef0250e3d..054aa1c0e 100755
--- a/src/conf_mode/protocols_rpki.py
+++ b/src/conf_mode/protocols_rpki.py
@@ -18,6 +18,7 @@ import os
from glob import glob
from sys import exit
+from sys import argv
from vyos.config import Config
from vyos.configverify import has_frr_protocol_in_dict
@@ -39,13 +40,18 @@ def get_config(config=None):
conf = config
else:
conf = Config()
- return get_frrender_dict(conf)
+ return get_frrender_dict(conf, argv)
def verify(config_dict):
if not has_frr_protocol_in_dict(config_dict, 'rpki'):
return None
- rpki = config_dict['rpki']
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ rpki = vrf and config_dict['vrf']['name'][vrf]['protocols']['rpki'] or config_dict['rpki']
if 'cache' in rpki:
preferences = []
@@ -79,7 +85,12 @@ def generate(config_dict):
if not has_frr_protocol_in_dict(config_dict, 'rpki'):
return None
- rpki = config_dict['rpki']
+ vrf = None
+ if 'vrf_context' in config_dict:
+ vrf = config_dict['vrf_context']
+
+ # eqivalent of the C foo ? 'a' : 'b' statement
+ rpki = vrf and config_dict['vrf']['name'][vrf]['protocols']['rpki'] or config_dict['rpki']
if 'cache' in rpki:
for cache, cache_config in rpki['cache'].items():
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 8baf55857..6e9d4147a 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -18,6 +18,8 @@ from sys import exit
from jmespath import search
from json import loads
+import vyos.defaults
+
from vyos.config import Config
from vyos.configdict import node_changed
from vyos.configverify import verify_route_map
@@ -163,6 +165,11 @@ def verify(vrf):
if 'table' not in vrf_config:
raise ConfigError(f'VRF "{name}" table id is mandatory!')
+ if int(vrf_config['table']) == vyos.defaults.rt_global_vrf:
+ raise ConfigError(
+ f'VRF "{name}" table id {vrf_config["table"]} cannot be used!'
+ )
+
# routing table id can't be changed - OS restriction
if interface_exists(name):
tmp = get_vrf_tableid(name)
diff --git a/src/helpers/reset_section.py b/src/helpers/reset_section.py
new file mode 100755
index 000000000..32857f650
--- /dev/null
+++ b/src/helpers/reset_section.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2025 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+
+import argparse
+import sys
+import os
+import grp
+
+from vyos.configsession import ConfigSession
+from vyos.config import Config
+from vyos.configdiff import get_config_diff
+from vyos.xml_ref import is_leaf
+
+
+CFG_GROUP = 'vyattacfg'
+DEBUG = False
+
+
+def type_str_to_list(value):
+ if isinstance(value, str):
+ return value.split()
+ raise argparse.ArgumentTypeError('path must be a whitespace separated string')
+
+
+parser = argparse.ArgumentParser()
+parser.add_argument('path', type=type_str_to_list, help='section to reload/rollback')
+parser.add_argument('--pid', help='pid of config session')
+
+group = parser.add_mutually_exclusive_group()
+group.add_argument('--reload', action='store_true', help='retry proposed commit')
+group.add_argument(
+ '--rollback', action='store_true', default=True, help='rollback to stable commit'
+)
+
+args = parser.parse_args()
+
+path = args.path
+reload = args.reload
+rollback = args.rollback
+pid = args.pid
+
+try:
+ if is_leaf(path):
+ sys.exit('path is leaf node: neither allowed nor useful')
+except ValueError:
+ if DEBUG:
+ sys.exit('nonexistent path: neither allowed nor useful')
+ else:
+ sys.exit()
+
+test = Config()
+in_session = test.in_session()
+
+if in_session:
+ if reload:
+ sys.exit('reset_section reload not available inside of a config session')
+
+ diff = get_config_diff(test)
+ if not diff.is_node_changed(path):
+ # No discrepancies at path after commit, hence no error to revert.
+ sys.exit()
+
+ del diff
+else:
+ if not reload:
+ sys.exit('reset_section rollback not available outside of a config session')
+
+del test
+
+
+session_id = int(pid) if pid else os.getppid()
+
+if in_session:
+ # check hint left by vyshim when ConfigError is from apply stage
+ hint_name = f'/tmp/apply_{session_id}'
+ if not os.path.exists(hint_name):
+ # no apply error; exit
+ sys.exit()
+ else:
+ # cleanup hint and continue with reset
+ os.unlink(hint_name)
+
+cfg_group = grp.getgrnam(CFG_GROUP)
+os.setgid(cfg_group.gr_gid)
+os.umask(0o002)
+
+shared = not bool(reload)
+
+session = ConfigSession(session_id, shared=shared)
+
+session_env = session.get_session_env()
+config = Config(session_env)
+
+d = config.get_config_dict(path, effective=True, get_first_key=True)
+
+if in_session:
+ session.discard()
+
+session.delete(path)
+session.commit()
+
+if not d:
+ # nothing more to do in either case of reload/rollback
+ sys.exit()
+
+session.set_section(path, d)
+out = session.commit()
+print(out)
diff --git a/src/migration-scripts/container/2-to-3 b/src/migration-scripts/container/2-to-3
new file mode 100644
index 000000000..54c6ec4c8
--- /dev/null
+++ b/src/migration-scripts/container/2-to-3
@@ -0,0 +1,31 @@
+# Copyright 2024 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# T7473: container: allow log-driver to be set per container
+
+from vyos.configtree import ConfigTree
+
+def migrate(config: ConfigTree) -> None:
+ log_base = ['container', 'log-driver']
+ container_base = ['container', 'name']
+
+ if not config.exists(log_base):
+ return
+ else:
+ log_driver = config.return_value(log_base)
+ for container in config.list_nodes(container_base):
+ # Set the log-driver for each container
+ config.set(container_base + [container, 'log-driver'], value=log_driver)
+ config.delete(log_base)
diff --git a/src/migration-scripts/firewall/18-to-19 b/src/migration-scripts/firewall/18-to-19
new file mode 100644
index 000000000..3564e0e01
--- /dev/null
+++ b/src/migration-scripts/firewall/18-to-19
@@ -0,0 +1,35 @@
+# Copyright (C) 2024-2025 VyOS maintainers and contributors
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+# From
+# set firewall global-options apply-to-bridged-traffic invalid-connections
+# To
+# set firewall global-options apply-to-bridged-traffic accept-invalid ethernet-type <ethertype>
+
+from vyos.configtree import ConfigTree
+
+base = ['firewall', 'global-options', 'apply-to-bridged-traffic']
+
+def migrate(config: ConfigTree) -> None:
+ if not config.exists(base + ['invalid-connections']):
+ # Nothing to do
+ return
+
+ ether_types = ['dhcp', 'arp', 'pppoe-discovery', 'pppoe', '802.1q', '802.1ad', 'wol']
+
+ for ether_type in ether_types:
+ config.set(base + ['accept-invalid', 'ethernet-type'], value=ether_type, replace=False)
+
+ config.delete(base + ['invalid-connections'])
diff --git a/src/migration-scripts/quagga/8-to-9 b/src/migration-scripts/quagga/8-to-9
index eece6c15d..c28e07e5c 100644
--- a/src/migration-scripts/quagga/8-to-9
+++ b/src/migration-scripts/quagga/8-to-9
@@ -16,6 +16,7 @@
# - T2450: drop interface-route and interface-route6 from "protocols static"
from vyos.configtree import ConfigTree
+from vyos.template import is_ip
def migrate_interface_route(config, base, path, route_route6):
""" Generic migration function which can be called on every instance of
@@ -31,11 +32,18 @@ def migrate_interface_route(config, base, path, route_route6):
tmp = base + path + [route, 'next-hop-interface']
for interface in config.list_nodes(tmp):
- new_base = base + [route_route6, route, 'interface']
- config.set(new_base)
- config.set_tag(base + [route_route6])
- config.set_tag(new_base)
- config.copy(tmp + [interface], new_base + [interface])
+ if is_ip(interface): # not prohibited in 1.3.x, hence allowed
+ new_base = base + [route_route6, route, 'next-hop']
+ config.set(new_base)
+ config.set_tag(base + [route_route6])
+ config.set_tag(new_base)
+ config.copy(tmp + [interface], new_base + [interface])
+ else:
+ new_base = base + [route_route6, route, 'interface']
+ config.set(new_base)
+ config.set_tag(base + [route_route6])
+ config.set_tag(new_base)
+ config.copy(tmp + [interface], new_base + [interface])
config.delete(base + path)
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index 05f65df1f..f93df0fc4 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -16,6 +16,7 @@
import json
import sys
+import subprocess
from vyos.utils.process import cmd
from vyos.utils.process import rc_cmd
@@ -109,6 +110,47 @@ def restart(name: str):
print(f'Container "{name}" restarted!')
return output
+def show_log(name: str, follow: bool = False, raw: bool = False):
+ """
+ Show or monitor logs for a specific container.
+ Use --follow to continuously stream logs.
+ """
+ from vyos.configquery import ConfigTreeQuery
+ conf = ConfigTreeQuery()
+ container = conf.get_config_dict(['container', 'name', name], get_first_key=True, with_recursive_defaults=True)
+ log_type = container.get('log-driver')
+ if log_type == 'k8s-file':
+ if follow:
+ log_command_list = ['sudo', 'podman', 'logs', '--follow', '--names', name]
+ else:
+ log_command_list = ['sudo', 'podman', 'logs', '--names', name]
+ elif log_type == 'journald':
+ if follow:
+ log_command_list = ['journalctl', '--follow', '--unit', f'vyos-container-{name}.service']
+ else:
+ log_command_list = ['journalctl', '-e', '--no-pager', '--unit', f'vyos-container-{name}.service']
+ elif log_type == 'none':
+ print(f'Container "{name}" has disabled logs.')
+ return None
+ else:
+ raise vyos.opmode.InternalError(f'Unknown log type "{log_type}" for container "{name}".')
+
+ process = None
+ try:
+ process = subprocess.Popen(log_command_list,
+ stdout=sys.stdout,
+ stderr=sys.stderr)
+ process.wait()
+ except KeyboardInterrupt:
+ if process:
+ process.terminate()
+ process.wait()
+ return None
+ except Exception as e:
+ raise vyos.opmode.InternalError(f"Error starting logging command: {e} ")
+ return None
+
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 28acccd2c..c45d492f9 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -68,6 +68,7 @@ class Response(Enum):
ERROR_COMMIT = 2
ERROR_DAEMON = 4
PASS = 8
+ ERROR_COMMIT_APPLY = 16
vyos_conf_scripts_dir = directories['conf_mode']
@@ -142,8 +143,6 @@ def run_script(script_name, config, args) -> tuple[Response, str]:
try:
c = script.get_config(config)
script.verify(c)
- script.generate(c)
- script.apply(c)
except ConfigError as e:
logger.error(e)
return Response.ERROR_COMMIT, str(e)
@@ -152,6 +151,17 @@ def run_script(script_name, config, args) -> tuple[Response, str]:
logger.error(tb)
return Response.ERROR_COMMIT, tb
+ try:
+ script.generate(c)
+ script.apply(c)
+ except ConfigError as e:
+ logger.error(e)
+ return Response.ERROR_COMMIT_APPLY, str(e)
+ except Exception:
+ tb = traceback.format_exc()
+ logger.error(tb)
+ return Response.ERROR_COMMIT_APPLY, tb
+
return Response.SUCCESS, ''
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index 1eb653cbf..35f995419 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -18,8 +18,10 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <fcntl.h>
#include <unistd.h>
#include <string.h>
+#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <stdint.h>
@@ -55,15 +57,17 @@ enum {
SUCCESS = 1 << 0,
ERROR_COMMIT = 1 << 1,
ERROR_DAEMON = 1 << 2,
- PASS = 1 << 3
+ PASS = 1 << 3,
+ ERROR_COMMIT_APPLY = 1 << 4
};
volatile int init_alarm = 0;
volatile int timeout = 0;
-int initialization(void *);
+int initialization(void *, char *);
int pass_through(char **, int);
void timer_handler(int);
+void leave_hint(char *);
double get_posix_clock_time(void);
@@ -94,8 +98,17 @@ int main(int argc, char* argv[])
char *test = strstr(string_node_data, "VYOS_TAGNODE_VALUE");
ex_index = test ? 2 : 1;
+ char *env_tmp = getenv("VYATTA_CONFIG_TMP");
+ if (env_tmp == NULL) {
+ fprintf(stderr, "Error: Environment variable VYATTA_CONFIG_TMP is not set.\n");
+ exit(EXIT_FAILURE);
+ }
+ char *pid_str = strdup(env_tmp);
+ strsep(&pid_str, "_");
+ debug_print("config session pid: %s\n", pid_str);
+
if (access(COMMIT_MARKER, F_OK) != -1) {
- init_timeout = initialization(requester);
+ init_timeout = initialization(requester, pid_str);
if (!init_timeout) remove(COMMIT_MARKER);
}
@@ -151,13 +164,19 @@ int main(int argc, char* argv[])
ret = -1;
}
+ if (err & ERROR_COMMIT_APPLY) {
+ debug_print("Received ERROR_COMMIT_APPLY\n");
+ leave_hint(pid_str);
+ ret = -1;
+ }
+
zmq_close(requester);
zmq_ctx_destroy(context);
return ret;
}
-int initialization(void* Requester)
+int initialization(void* Requester, char* pid_val)
{
char *active_str = NULL;
size_t active_len = 0;
@@ -185,10 +204,6 @@ int initialization(void* Requester)
double prev_time_value, time_value;
double time_diff;
- char *pid_val = getenv("VYATTA_CONFIG_TMP");
- strsep(&pid_val, "_");
- debug_print("config session pid: %s\n", pid_val);
-
char *sudo_user = getenv("SUDO_USER");
if (!sudo_user) {
char nobody[] = "nobody";
@@ -338,6 +353,16 @@ void timer_handler(int signum)
return;
}
+void leave_hint(char *pid_val)
+{
+ char tmp_str[16];
+ mode_t omask = umask(0);
+ snprintf(tmp_str, sizeof(tmp_str), "/tmp/apply_%s", pid_val);
+ open(tmp_str, O_CREAT|O_RDWR|O_TRUNC, 0666);
+ chown(tmp_str, 1002, 102);
+ umask(omask);
+}
+
#ifdef _POSIX_MONOTONIC_CLOCK
double get_posix_clock_time(void)
{
diff --git a/src/tests/test_template.py b/src/tests/test_template.py
index 4660c0038..09315d398 100644
--- a/src/tests/test_template.py
+++ b/src/tests/test_template.py
@@ -199,8 +199,12 @@ class TestVyOSTemplate(TestCase):
vyos.template.get_default_config_file('UNKNOWN')
with self.assertRaises(RuntimeError):
vyos.template.get_default_port('UNKNOWN')
+ with self.assertRaises(RuntimeError):
+ vyos.template.nft_accept_invalid('UNKNOWN')
self.assertEqual(vyos.template.get_default_config_file('sshd_user_ca'),
config_files['sshd_user_ca'])
self.assertEqual(vyos.template.get_default_port('certbot_haproxy'),
internal_ports['certbot_haproxy'])
+ self.assertEqual(vyos.template.nft_accept_invalid('arp'),
+ 'ct state invalid ether type arp counter accept')