summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint-with-ruff.yml14
-rw-r--r--.github/workflows/trigger-rebuild-repo-package.yml33
-rw-r--r--Makefile1
-rw-r--r--data/templates/accel-ppp/pppoe.config.j26
-rw-r--r--data/templates/dns-forwarding/recursor.conf.lua.j228
-rw-r--r--data/templates/firewall/nftables-nat66.j224
-rwxr-xr-xdata/templates/firewall/nftables.j28
-rw-r--r--data/templates/router-advert/radvd.conf.j22
-rw-r--r--data/templates/wifi/hostapd.conf.j212
-rw-r--r--debian/control1
-rw-r--r--debian/vyos-1x.postinst3
-rw-r--r--interface-definitions/container.xml.in6
-rw-r--r--interface-definitions/include/firewall/common-rule-bridge.xml.i1
-rw-r--r--interface-definitions/include/firewall/global-options.xml.i6
-rw-r--r--interface-definitions/include/firewall/match-ether-type.xml.i30
-rw-r--r--interface-definitions/include/firewall/match-vlan.xml.i1
-rw-r--r--interface-definitions/interfaces_wireless.xml.in68
-rw-r--r--interface-definitions/nat66.xml.in1
-rw-r--r--interface-definitions/service_dns_forwarding.xml.in173
-rw-r--r--interface-definitions/service_pppoe-server.xml.in12
-rw-r--r--interface-definitions/service_router-advert.xml.in6
-rw-r--r--op-mode-definitions/execute-bandwidth-test.xml.in (renamed from op-mode-definitions/monitor-bandwidth-test.xml.in)6
-rw-r--r--op-mode-definitions/execute-port-scan.xml.in34
-rw-r--r--op-mode-definitions/execute-shell.xml.in32
-rw-r--r--op-mode-definitions/execute-wamp.xml.in (renamed from op-mode-definitions/force-wamp.xml.in)2
-rw-r--r--op-mode-definitions/force-netns.xml.in16
-rw-r--r--op-mode-definitions/force-vrf.xml.in16
-rw-r--r--op-mode-definitions/ntp.xml.in8
-rw-r--r--op-mode-definitions/show-interfaces-macsec.xml.in8
-rw-r--r--op-mode-definitions/telnet.xml.in35
-rw-r--r--op-mode-definitions/wake-on-lan.xml.in32
-rw-r--r--python/vyos/configdep.py26
-rw-r--r--python/vyos/configdiff.py37
-rwxr-xr-xpython/vyos/firewall.py27
-rw-r--r--python/vyos/nat.py10
-rw-r--r--python/vyos/utils/convert.py62
-rw-r--r--python/vyos/utils/dict.py1
-rw-r--r--python/vyos/xml_ref/__init__.py6
-rw-r--r--python/vyos/xml_ref/definition.py22
-rwxr-xr-xsmoketest/scripts/cli/test_config_dependency.py85
-rwxr-xr-xsmoketest/scripts/cli/test_container.py34
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py14
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py89
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py30
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py39
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py16
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py29
-rwxr-xr-xsmoketest/scripts/cli/test_system_option.py15
-rwxr-xr-xsrc/conf_mode/container.py4
-rwxr-xr-xsrc/conf_mode/nat66.py28
-rwxr-xr-xsrc/conf_mode/service_dns_forwarding.py20
-rwxr-xr-xsrc/conf_mode/system_option.py2
-rw-r--r--src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper18
-rwxr-xr-xsrc/op_mode/execute_bandwidth_test.sh (renamed from src/op_mode/monitor_bandwidth_test.sh)0
-rw-r--r--src/op_mode/execute_port-scan.py155
-rw-r--r--src/op_mode/ntp.py45
-rwxr-xr-xsrc/op_mode/restart.py42
-rwxr-xr-xsrc/services/vyos-configd19
-rw-r--r--src/systemd/podman.service16
-rw-r--r--src/systemd/podman.socket10
60 files changed, 1362 insertions, 164 deletions
diff --git a/.github/workflows/lint-with-ruff.yml b/.github/workflows/lint-with-ruff.yml
new file mode 100644
index 000000000..00cc9ca1b
--- /dev/null
+++ b/.github/workflows/lint-with-ruff.yml
@@ -0,0 +1,14 @@
+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/.github/workflows/trigger-rebuild-repo-package.yml b/.github/workflows/trigger-rebuild-repo-package.yml
new file mode 100644
index 000000000..d0936b572
--- /dev/null
+++ b/.github/workflows/trigger-rebuild-repo-package.yml
@@ -0,0 +1,33 @@
+name: Trigger to build a deb package from repo
+
+on:
+ pull_request:
+ types:
+ - closed
+ branches:
+ - current
+ workflow_dispatch:
+
+jobs:
+ get_repo_name:
+ runs-on: ubuntu-latest
+ outputs:
+ PACKAGE_NAME: ${{ steps.package_name.outputs.PACKAGE_NAME }}
+ steps:
+ - name: Set variables
+ id: package_name
+ run: |
+ echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> $GITHUB_OUTPUT
+
+ trigger-build:
+ needs: get_repo_name
+ uses: vyos/.github/.github/workflows/trigger-rebuild-repo-package.yml@current
+ with:
+ branch: ${{ github.ref_name }}
+ package_name: ${{ needs.get_repo_name.outputs.PACKAGE_NAME }}
+ REF: main # optinal because the default value is main
+ secrets:
+ REMOTE_OWNER: ${{ secrets.REMOTE_OWNER }}
+ REMOTE_REUSE_REPO: ${{ secrets.REMOTE_REUSE_REPO }}
+ GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
+ PAT: ${{ secrets.PAT }}
diff --git a/Makefile b/Makefile
index c83380be5..411399c3a 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,7 @@ op_mode_definitions: $(op_xml_obj)
ln -s ../node.tag $(OP_TMPL_DIR)/mtr/node.tag/node.tag/
ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traceroute/node.tag/node.tag/
ln -s ../node.tag $(OP_TMPL_DIR)/monitor/traffic/interface/node.tag/node.tag/
+ ln -s ../node.tag $(OP_TMPL_DIR)/execute/port-scan/host/node.tag/node.tag/
# XXX: test if there are empty node.def files - this is not allowed as these
# could mask help strings or mandatory priority statements
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index beab46936..cf952c687 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -70,6 +70,12 @@ vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% if service_name %}
service-name={{ service_name | join(',') }}
{% endif %}
+{% if accept_any_service is vyos_defined %}
+accept-any-service=1
+{% endif %}
+{% if accept_blank_service is vyos_defined %}
+accept-blank-service=1
+{% endif %}
{% if pado_delay %}
{% set delay_without_sessions = pado_delay.delays_without_sessions[0] | default('0') %}
{% set pado_delay_param = namespace(value=delay_without_sessions) %}
diff --git a/data/templates/dns-forwarding/recursor.conf.lua.j2 b/data/templates/dns-forwarding/recursor.conf.lua.j2
index 8026442c7..622283ad8 100644
--- a/data/templates/dns-forwarding/recursor.conf.lua.j2
+++ b/data/templates/dns-forwarding/recursor.conf.lua.j2
@@ -6,3 +6,31 @@ dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua")
-- Load lua from vyos-hostsd --
dofile("{{ config_dir }}/recursor.vyos-hostsd.conf.lua")
+
+-- ZoneToCache --
+{% if zone_cache is vyos_defined %}
+{% set option_mapping = {
+ 'refresh': 'refreshPeriod',
+ 'retry_interval': 'retryOnErrorPeriod',
+ 'max_zone_size': 'maxReceivedMBytes'
+} %}
+{% for name, conf in zone_cache.items() %}
+{% set source = conf.source.items() | first %}
+{% set settings = [] %}
+{% for key, val in conf.options.items() %}
+{% set mapped_key = option_mapping.get(key, key) %}
+{% if key == 'refresh' %}
+{% set val = val['interval'] %}
+{% endif %}
+{% if key in ['dnssec', 'zonemd'] %}
+{% set _ = settings.append(mapped_key ~ ' = "' ~ val ~ '"') %}
+{% else %}
+{% set _ = settings.append(mapped_key ~ ' = ' ~ val) %}
+{% endif %}
+{% endfor %}
+
+zoneToCache("{{ name }}", "{{ source[0] }}", "{{ source[1] }}", { {{ settings | join(', ') }} })
+
+{% endfor %}
+
+{% endif %}
diff --git a/data/templates/firewall/nftables-nat66.j2 b/data/templates/firewall/nftables-nat66.j2
index 67eb2c109..09b5b6ac2 100644
--- a/data/templates/firewall/nftables-nat66.j2
+++ b/data/templates/firewall/nftables-nat66.j2
@@ -1,8 +1,11 @@
#!/usr/sbin/nft -f
+{% import 'firewall/nftables-defines.j2' as group_tmpl %}
+
{% if first_install is not vyos_defined %}
delete table ip6 vyos_nat
{% endif %}
+{% if deleted is not vyos_defined %}
table ip6 vyos_nat {
#
# Destination NAT66 rules build up here
@@ -10,11 +13,11 @@ table ip6 vyos_nat {
chain PREROUTING {
type nat hook prerouting priority -100; policy accept;
counter jump VYOS_DNPT_HOOK
-{% if destination.rule is vyos_defined %}
-{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
- {{ config | nat_rule(rule, 'destination', ipv6=True) }}
-{% endfor %}
-{% endif %}
+{% if destination.rule is vyos_defined %}
+{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
+ {{ config | nat_rule(rule, 'destination', ipv6=True) }}
+{% endfor %}
+{% endif %}
}
#
@@ -23,11 +26,11 @@ table ip6 vyos_nat {
chain POSTROUTING {
type nat hook postrouting priority 100; policy accept;
counter jump VYOS_SNPT_HOOK
-{% if source.rule is vyos_defined %}
-{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
+{% if source.rule is vyos_defined %}
+{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
{{ config | nat_rule(rule, 'source', ipv6=True) }}
-{% endfor %}
-{% endif %}
+{% endfor %}
+{% endif %}
}
chain VYOS_DNPT_HOOK {
@@ -37,4 +40,7 @@ table ip6 vyos_nat {
chain VYOS_SNPT_HOOK {
return
}
+
+{{ group_tmpl.groups(firewall_group, True, True) }}
}
+{% endif %}
diff --git a/data/templates/firewall/nftables.j2 b/data/templates/firewall/nftables.j2
index 155b7f4d0..034328400 100755
--- a/data/templates/firewall/nftables.j2
+++ b/data/templates/firewall/nftables.j2
@@ -376,8 +376,14 @@ table bridge vyos_filter {
{% if bridge.output is vyos_defined %}
{% for prior, conf in bridge.output.items() %}
- chain VYOS_OUTUT_{{ prior }} {
+ 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
+{% endif %}
+{% endif %}
{% if global_options.state_policy is vyos_defined %}
jump VYOS_STATE_POLICY
{% endif %}
diff --git a/data/templates/router-advert/radvd.conf.j2 b/data/templates/router-advert/radvd.conf.j2
index 97180d164..a83bd03ac 100644
--- a/data/templates/router-advert/radvd.conf.j2
+++ b/data/templates/router-advert/radvd.conf.j2
@@ -19,7 +19,7 @@ interface {{ iface }} {
{% if iface_config.reachable_time is vyos_defined %}
AdvReachableTime {{ iface_config.reachable_time }};
{% endif %}
- AdvIntervalOpt {{ 'off' if iface_config.no_send_advert is vyos_defined else 'on' }};
+ AdvIntervalOpt {{ 'off' if iface_config.no_send_interval is vyos_defined else 'on' }};
AdvSendAdvert {{ 'off' if iface_config.no_send_advert is vyos_defined else 'on' }};
{% if iface_config.default_lifetime is vyos_defined %}
AdvDefaultLifetime {{ iface_config.default_lifetime }};
diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2
index 0459fbc69..5f3757216 100644
--- a/data/templates/wifi/hostapd.conf.j2
+++ b/data/templates/wifi/hostapd.conf.j2
@@ -46,7 +46,14 @@ hw_mode=a
ieee80211h=1
ieee80211ac=1
{% elif mode is vyos_defined('ax') %}
+{#{% if capabilities.ht is vyos_defined and capabilities.vht not vyos_defined %}#}
+{% if capabilities.he.channel_set_width is vyos_defined('81') or capabilities.he.channel_set_width is vyos_defined('83') or capabilities.he.channel_set_width is vyos_defined('84') %}
+{# This is almost certainly a 2.4GHz network #}
+hw_mode=g
+{% else %}
+{# This is likely a 5GHz or 6GHz network #}
hw_mode=a
+{% endif %}
ieee80211h=1
ieee80211ax=1
{% else %}
@@ -202,7 +209,7 @@ require_he=1
{% else %}
ieee80211n={{ '1' if 'n' in mode or 'ac' in mode or 'ax' in mode else '0' }}
{% endif %}
-{# HE (802.11ax 6GHz) #}
+{# HE (802.11ax) #}
{% if capabilities.he is vyos_defined and mode in 'ax' %}
{# For now, hard-code power levels for indoor-only AP #}
he_6ghz_reg_pwr_type=0
@@ -220,6 +227,9 @@ op_class={{ capabilities.he.channel_set_width }}
{% if capabilities.he.bss_color is vyos_defined %}
he_bss_color={{ capabilities.he.bss_color }}
{% endif %}
+{% if capabilities.he.coding_scheme is vyos_defined %}
+he_basic_mcs_nss_set={{ capabilities.he.coding_scheme }}
+{% endif %}
he_6ghz_rx_ant_pat={{ '1' if capabilities.he.antenna_pattern_fixed is vyos_defined else '0' }}
he_su_beamformer={{ '1' if capabilities.he.beamform.single_user_beamformer is vyos_defined else '0' }}
he_su_beamformee={{ '1' if capabilities.he.beamform.single_user_beamformee is vyos_defined else '0' }}
diff --git a/debian/control b/debian/control
index d3f5fb464..890100fd8 100644
--- a/debian/control
+++ b/debian/control
@@ -149,6 +149,7 @@ Depends:
openvpn-auth-ldap,
openvpn-auth-radius,
openvpn-otp,
+ openvpn-dco,
libpam-google-authenticator,
# End "interfaces openvpn"
# For "interfaces wireguard"
diff --git a/debian/vyos-1x.postinst b/debian/vyos-1x.postinst
index 141a9e8f9..dc8ada267 100644
--- a/debian/vyos-1x.postinst
+++ b/debian/vyos-1x.postinst
@@ -244,6 +244,9 @@ fi
# Enable Cloud-init pre-configuration service
systemctl enable vyos-config-cloud-init.service
+# Enable Podman API
+systemctl enable podman.service
+
# Generate API GraphQL schema
/usr/libexec/vyos/services/api/graphql/generate/generate_schema.py
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index 6ea44a6d4..3dd1b3249 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -519,6 +519,12 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="no-name-server">
+ <properties>
+ <help>Disable Domain Name System (DNS) plugin for this network</help>
+ <valueless/>
+ </properties>
+ </leafNode>
#include <include/interface/vrf.xml.i>
</children>
</tagNode>
diff --git a/interface-definitions/include/firewall/common-rule-bridge.xml.i b/interface-definitions/include/firewall/common-rule-bridge.xml.i
index 9ae28f7be..80088bbec 100644
--- a/interface-definitions/include/firewall/common-rule-bridge.xml.i
+++ b/interface-definitions/include/firewall/common-rule-bridge.xml.i
@@ -10,6 +10,7 @@
#include <include/firewall/limit.xml.i>
#include <include/firewall/log.xml.i>
#include <include/firewall/log-options.xml.i>
+#include <include/firewall/match-ether-type.xml.i>
#include <include/firewall/match-ipsec.xml.i>
#include <include/firewall/match-vlan.xml.i>
#include <include/firewall/nft-queue.xml.i>
diff --git a/interface-definitions/include/firewall/global-options.xml.i b/interface-definitions/include/firewall/global-options.xml.i
index cee8f1854..05fdd75cb 100644
--- a/interface-definitions/include/firewall/global-options.xml.i
+++ b/interface-definitions/include/firewall/global-options.xml.i
@@ -49,6 +49,12 @@
<help>Apply configured firewall rules to traffic switched by bridges</help>
</properties>
<children>
+ <leafNode name="invalid-connections">
+ <properties>
+ <help>Accept ARP and DHCP despite they are marked as invalid connection</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="ipv4">
<properties>
<help>Apply configured IPv4 firewall rules</help>
diff --git a/interface-definitions/include/firewall/match-ether-type.xml.i b/interface-definitions/include/firewall/match-ether-type.xml.i
new file mode 100644
index 000000000..abfa9034d
--- /dev/null
+++ b/interface-definitions/include/firewall/match-ether-type.xml.i
@@ -0,0 +1,30 @@
+<!-- include start from firewall/match-ether-type.xml.i -->
+<leafNode name="ethernet-type">
+ <properties>
+ <help>Ethernet type</help>
+ <completionHelp>
+ <list>802.1q 802.1ad arp ipv4 ipv6</list>
+ </completionHelp>
+ <valueHelp>
+ <format>802.1q</format>
+ <description>Customer VLAN tag type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>802.1ad</format>
+ <description>Service VLAN tag type</description>
+ </valueHelp>
+ <valueHelp>
+ <format>arp</format>
+ <description>Adress Resolution Protocol</description>
+ </valueHelp>
+ <valueHelp>
+ <format>_ipv4</format>
+ <description>Internet Protocol version 4</description>
+ </valueHelp>
+ <valueHelp>
+ <format>_ipv6</format>
+ <description>Internet Protocol version 6</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/firewall/match-vlan.xml.i b/interface-definitions/include/firewall/match-vlan.xml.i
index 44ad02c99..d58e84353 100644
--- a/interface-definitions/include/firewall/match-vlan.xml.i
+++ b/interface-definitions/include/firewall/match-vlan.xml.i
@@ -36,6 +36,7 @@
</constraint>
</properties>
</leafNode>
+ #include <include/firewall/match-ether-type.xml.i>
</children>
</node>
<!-- include end --> \ No newline at end of file
diff --git a/interface-definitions/interfaces_wireless.xml.in b/interface-definitions/interfaces_wireless.xml.in
index fdcb79b19..474953500 100644
--- a/interface-definitions/interfaces_wireless.xml.in
+++ b/interface-definitions/interfaces_wireless.xml.in
@@ -248,26 +248,26 @@
<properties>
<help>VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes)</help>
<valueHelp>
- <format>u32:34-173</format>
+ <format>u32:34-177</format>
<description>5Ghz (802.11 a/h/j/n/ac) center channel index (use 42 for primary 80MHz channel 36)</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 34-173"/>
+ <validator name="numeric" argument="--range 34-177"/>
</constraint>
- <constraintErrorMessage>Channel center value must be between 34 and 173</constraintErrorMessage>
+ <constraintErrorMessage>Channel center value must be between 34 and 177</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="freq-2">
<properties>
<help>VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode)</help>
<valueHelp>
- <format>u32:34-173</format>
+ <format>u32:34-177</format>
<description>5Ghz (802.11 ac) center channel index (use 58 for secondary 80MHz channel 52)</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 34-173"/>
+ <validator name="numeric" argument="--range 34-177"/>
</constraint>
- <constraintErrorMessage>Channel center value must be between 34 and 173</constraintErrorMessage>
+ <constraintErrorMessage>Channel center value must be between 34 and 177</constraintErrorMessage>
</properties>
</leafNode>
</children>
@@ -436,30 +436,42 @@
https://w1.fi/cgit/hostap/tree/src/common/ieee802_11_common.c?id=195cc3d919503fb0d699d9a56a58a72602b25f51#n1525
802.11ax (WiFi-6e - HE) can use up to 160MHz bandwidth channels
-->
- <list>131 132 133 134 135</list>
+ <list>81 83 84 131 132 133 134 135</list>
</completionHelp>
<valueHelp>
+ <format>81</format>
+ <description>2.4GHz, 20 MHz channel width</description>
+ </valueHelp>
+ <valueHelp>
+ <format>83</format>
+ <description>2.4GHz, 40 MHz channel width, secondary 20MHz channel above primary channel</description>
+ </valueHelp>
+ <valueHelp>
+ <format>84</format>
+ <description>2.4GHz, 40 MHz channel width, secondary 20MHz channel below primary channel</description>
+ </valueHelp>
+ <valueHelp>
<format>131</format>
- <description>20 MHz channel width</description>
+ <description>6GHz, 20 MHz channel width</description>
</valueHelp>
<valueHelp>
<format>132</format>
- <description>40 MHz channel width</description>
+ <description>6GHz, 40 MHz channel width</description>
</valueHelp>
<valueHelp>
<format>133</format>
- <description>80 MHz channel width</description>
+ <description>6GHz, 80 MHz channel width</description>
</valueHelp>
<valueHelp>
<format>134</format>
- <description>160 MHz channel width</description>
+ <description>6GHz, 160 MHz channel width</description>
</valueHelp>
<valueHelp>
<format>135</format>
- <description>80+80 MHz channel width</description>
+ <description>6GHz, 80+80 MHz channel width</description>
</valueHelp>
<constraint>
- <regex>(131|132|133|134|135)</regex>
+ <regex>(81|83|84|131|132|133|134|135)</regex>
</constraint>
</properties>
</leafNode>
@@ -535,6 +547,30 @@
</constraint>
</properties>
</leafNode>
+ <leafNode name="coding-scheme">
+ <properties>
+ <help>Spacial Stream and Modulation Coding Scheme settings</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>HE-MCS 0-7</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1</format>
+ <description>HE-MCS 0-9</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:2</format>
+ <description>HE-MCS 0-11</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:3</format>
+ <description>HE-MCS is not supported</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-3"/>
+ </constraint>
+ </properties>
+ </leafNode>
</children>
</node>
<leafNode name="require-he">
@@ -554,10 +590,10 @@
</valueHelp>
<valueHelp>
<format>u32:1-14</format>
- <description>2.4Ghz (802.11 b/g/n) Channel</description>
+ <description>2.4Ghz (802.11 b/g/n/ax) Channel</description>
</valueHelp>
<valueHelp>
- <format>u32:34-173</format>
+ <format>u32:34-177</format>
<description>5Ghz (802.11 a/h/j/n/ac) Channel</description>
</valueHelp>
<valueHelp>
@@ -565,7 +601,7 @@
<description>6Ghz (802.11 ax) Channel</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 0-0 --range 1-14 --range 34-173 --range 1-233"/>
+ <validator name="numeric" argument="--range 0-0 --range 1-14 --range 34-177 --range 1-233"/>
</constraint>
</properties>
<defaultValue>0</defaultValue>
diff --git a/interface-definitions/nat66.xml.in b/interface-definitions/nat66.xml.in
index 32d501cce..c59725c53 100644
--- a/interface-definitions/nat66.xml.in
+++ b/interface-definitions/nat66.xml.in
@@ -179,6 +179,7 @@
</properties>
</leafNode>
#include <include/nat-port.xml.i>
+ #include <include/firewall/source-destination-group-ipv6.xml.i>
</children>
</node>
<node name="source">
diff --git a/interface-definitions/service_dns_forwarding.xml.in b/interface-definitions/service_dns_forwarding.xml.in
index 5667028b7..d0bc2e6c8 100644
--- a/interface-definitions/service_dns_forwarding.xml.in
+++ b/interface-definitions/service_dns_forwarding.xml.in
@@ -793,6 +793,179 @@
</leafNode>
</children>
</node>
+ <tagNode name="zone-cache">
+ <properties>
+ <help>Load a zone into the recursor cache</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain name</description>
+ </valueHelp>
+ <constraint>
+ <validator name="fqdn"/>
+ </constraint>
+ </properties>
+ <children>
+ <node name="source">
+ <properties>
+ <help>Zone source</help>
+ </properties>
+ <children>
+ <leafNode name="axfr">
+ <properties>
+ <help>DNS server address</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ip-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="url">
+ <properties>
+ <help>Source URL</help>
+ <valueHelp>
+ <format>url</format>
+ <description>Zone file URL</description>
+ </valueHelp>
+ <constraint>
+ <validator name="url" argument="--scheme http --scheme https"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <node name="options">
+ <properties>
+ <help>Zone caching options</help>
+ </properties>
+ <children>
+ <leafNode name="timeout">
+ <properties>
+ <help>Zone retrieval timeout</help>
+ <valueHelp>
+ <format>u32:1-3600</format>
+ <description>Request timeout in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-3600"/>
+ </constraint>
+ </properties>
+ <defaultValue>20</defaultValue>
+ </leafNode>
+ <node name="refresh">
+ <properties>
+ <help>Zone caching options</help>
+ </properties>
+ <children>
+ <leafNode name="on-reload">
+ <properties>
+ <help>Retrieval zone only at startup and on reload</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="interval">
+ <properties>
+ <help>Periodic zone retrieval interval</help>
+ <valueHelp>
+ <format>u32:0-31536000</format>
+ <description>Retrieval interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-31536000"/>
+ </constraint>
+ </properties>
+ <defaultValue>86400</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="retry-interval">
+ <properties>
+ <help>Retry interval after zone retrieval errors</help>
+ <valueHelp>
+ <format>u32:1-86400</format>
+ <description>Retry period in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-86400"/>
+ </constraint>
+ </properties>
+ <defaultValue>60</defaultValue>
+ </leafNode>
+ <leafNode name="max-zone-size">
+ <properties>
+ <help>Maximum zone size in megabytes</help>
+ <valueHelp>
+ <format>u32:0</format>
+ <description>No restriction</description>
+ </valueHelp>
+ <valueHelp>
+ <format>u32:1-1024</format>
+ <description>Size in megabytes</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-1024"/>
+ </constraint>
+ </properties>
+ <defaultValue>0</defaultValue>
+ </leafNode>
+ <leafNode name="zonemd">
+ <properties>
+ <help>Message Digest for DNS Zones (RFC 8976)</help>
+ <completionHelp>
+ <list>ignore validate require</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ignore</format>
+ <description>Ignore ZONEMD records</description>
+ </valueHelp>
+ <valueHelp>
+ <format>validate</format>
+ <description>Validate ZONEMD if present</description>
+ </valueHelp>
+ <valueHelp>
+ <format>require</format>
+ <description>Require valid ZONEMD record to be present</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ignore|validate|require)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>validate</defaultValue>
+ </leafNode>
+ <leafNode name="dnssec">
+ <properties>
+ <help>DNSSEC mode</help>
+ <completionHelp>
+ <list>ignore validate require</list>
+ </completionHelp>
+ <valueHelp>
+ <format>ignore</format>
+ <description>Do not do DNSSEC validation</description>
+ </valueHelp>
+ <valueHelp>
+ <format>validate</format>
+ <description>Reject zones with incorrect signatures but accept unsigned zones</description>
+ </valueHelp>
+ <valueHelp>
+ <format>require</format>
+ <description>Require DNSSEC validation</description>
+ </valueHelp>
+ <constraint>
+ <regex>(ignore|validate|require)</regex>
+ </constraint>
+ </properties>
+ <defaultValue>validate</defaultValue>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </tagNode>
</children>
</node>
</children>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in
index 93ec7ade9..0c99fd261 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service_pppoe-server.xml.in
@@ -77,6 +77,18 @@
<multi/>
</properties>
</leafNode>
+ <leafNode name="accept-any-service">
+ <properties>
+ <help>Accept any service name in PPPoE Active Discovery Request (PADR)</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="accept-blank-service">
+ <properties>
+ <help>Accept blank service name in PADR</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<tagNode name="pado-delay">
<properties>
<help>PADO delays</help>
diff --git a/interface-definitions/service_router-advert.xml.in b/interface-definitions/service_router-advert.xml.in
index 166a4a0cf..3fd33540a 100644
--- a/interface-definitions/service_router-advert.xml.in
+++ b/interface-definitions/service_router-advert.xml.in
@@ -390,6 +390,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="no-send-interval">
+ <properties>
+ <help>Do not send Advertisement Interval option in RAs</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/monitor-bandwidth-test.xml.in b/op-mode-definitions/execute-bandwidth-test.xml.in
index 965591280..1581d5c25 100644
--- a/op-mode-definitions/monitor-bandwidth-test.xml.in
+++ b/op-mode-definitions/execute-bandwidth-test.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="monitor">
+ <node name="execute">
<children>
<node name="bandwidth-test">
<properties>
@@ -39,7 +39,7 @@
<list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/monitor_bandwidth_test.sh "$5"</command>
+ <command>${vyos_op_scripts_dir}/execute_bandwidth_test.sh "$5"</command>
</tagNode>
<tagNode name="udp">
<properties>
@@ -48,7 +48,7 @@
<list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/monitor_bandwidth_test.sh "$5" "-u"</command>
+ <command>${vyos_op_scripts_dir}/execute_bandwidth_test.sh "$5" "-u"</command>
</tagNode>
</children>
</node>
diff --git a/op-mode-definitions/execute-port-scan.xml.in b/op-mode-definitions/execute-port-scan.xml.in
new file mode 100644
index 000000000..52cdab5f0
--- /dev/null
+++ b/op-mode-definitions/execute-port-scan.xml.in
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <children>
+ <node name="port-scan">
+ <properties>
+ <help>Scan network for open ports</help>
+ </properties>
+ <children>
+ <tagNode name="host">
+ <properties>
+ <help>IP address or domain name of the host to scan (scan all ports 1-65535)</help>
+ <completionHelp>
+ <list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>nmap -p- -T4 --max-retries=1 --host-timeout=30s "$4"</command>
+ <children>
+ <leafNode name="node.tag">
+ <properties>
+ <help>Port scan options</help>
+ <completionHelp>
+ <script>${vyos_op_scripts_dir}/execute_port-scan.py --get-options-nested "${COMP_WORDS[@]}"</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/execute_port-scan.py "${@:4}"</command>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/execute-shell.xml.in b/op-mode-definitions/execute-shell.xml.in
new file mode 100644
index 000000000..dfdc1e371
--- /dev/null
+++ b/op-mode-definitions/execute-shell.xml.in
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <children>
+ <node name="shell">
+ <properties>
+ <help>Execute shell</help>
+ </properties>
+ <children>
+ <tagNode name="netns">
+ <properties>
+ <help>Execute shell in given Network Namespace</help>
+ <completionHelp>
+ <path>netns name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ip netns exec $4 su - $(whoami)</command>
+ </tagNode>
+ <tagNode name="vrf">
+ <properties>
+ <help>Execute shell in given VRF instance</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ip vrf exec $4 su - $(whoami)</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/force-wamp.xml.in b/op-mode-definitions/execute-wamp.xml.in
index dbb205c6b..bcceedc53 100644
--- a/op-mode-definitions/force-wamp.xml.in
+++ b/op-mode-definitions/execute-wamp.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="force">
+ <node name="execute">
<children>
<tagNode name="owping">
<properties>
diff --git a/op-mode-definitions/force-netns.xml.in b/op-mode-definitions/force-netns.xml.in
deleted file mode 100644
index b9dc2c1e8..000000000
--- a/op-mode-definitions/force-netns.xml.in
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="force">
- <children>
- <tagNode name="netns">
- <properties>
- <help>Execute shell in given Network Namespace</help>
- <completionHelp>
- <path>netns name</path>
- </completionHelp>
- </properties>
- <command>sudo ip netns exec $3 su - $(whoami)</command>
- </tagNode>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/force-vrf.xml.in b/op-mode-definitions/force-vrf.xml.in
deleted file mode 100644
index 71f50b0d2..000000000
--- a/op-mode-definitions/force-vrf.xml.in
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="force">
- <children>
- <tagNode name="vrf">
- <properties>
- <help>Execute shell in given VRF instance</help>
- <completionHelp>
- <path>vrf name</path>
- </completionHelp>
- </properties>
- <command>sudo ip vrf exec $3 su - $(whoami)</command>
- </tagNode>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/ntp.xml.in b/op-mode-definitions/ntp.xml.in
index 17250a45e..565a5edb5 100644
--- a/op-mode-definitions/ntp.xml.in
+++ b/op-mode-definitions/ntp.xml.in
@@ -6,25 +6,25 @@
<properties>
<help>Show peer status of NTP daemon</help>
</properties>
- <command>${vyos_op_scripts_dir}/ntp.py show_sourcestats</command>
+ <command>sudo ${vyos_op_scripts_dir}/ntp.py show_sourcestats</command>
<children>
<node name="activity">
<properties>
<help>Report the number of servers and peers that are online and offline</help>
</properties>
- <command>${vyos_op_scripts_dir}/ntp.py show_activity</command>
+ <command>sudo ${vyos_op_scripts_dir}/ntp.py show_activity</command>
</node>
<node name="sources">
<properties>
<help>Show information about the current time sources being accessed</help>
</properties>
- <command>${vyos_op_scripts_dir}/ntp.py show_sources</command>
+ <command>sudo ${vyos_op_scripts_dir}/ntp.py show_sources</command>
</node>
<node name="system">
<properties>
<help>Show parameters about the system clock performance</help>
</properties>
- <command>${vyos_op_scripts_dir}/ntp.py show_tracking</command>
+ <command>sudo ${vyos_op_scripts_dir}/ntp.py show_tracking</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-macsec.xml.in b/op-mode-definitions/show-interfaces-macsec.xml.in
index a264ff22e..28264d252 100644
--- a/op-mode-definitions/show-interfaces-macsec.xml.in
+++ b/op-mode-definitions/show-interfaces-macsec.xml.in
@@ -12,6 +12,14 @@
</completionHelp>
</properties>
<command>ip macsec show</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed MACsec interface information</help>
+ </properties>
+ <command>ip -s macsec show</command>
+ </leafNode>
+ </children>
</node>
<tagNode name="macsec">
<properties>
diff --git a/op-mode-definitions/telnet.xml.in b/op-mode-definitions/telnet.xml.in
index c5bb6d283..2cacc6a26 100644
--- a/op-mode-definitions/telnet.xml.in
+++ b/op-mode-definitions/telnet.xml.in
@@ -1,30 +1,35 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="telnet">
- <properties>
- <help>Telnet to a node</help>
- </properties>
+ <node name="execute">
<children>
- <tagNode name="to">
+ <node name="telnet">
<properties>
- <help>Telnet to a host</help>
- <completionHelp>
- <list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
- </completionHelp>
+ <help>Telnet to a node</help>
</properties>
- <command>/usr/bin/telnet $3</command>
<children>
- <tagNode name="port">
+ <tagNode name="to">
<properties>
- <help>Telnet to a host:port</help>
+ <help>Telnet to a host</help>
<completionHelp>
- <list>&lt;0-65535&gt;</list>
+ <list>&lt;hostname&gt; &lt;x.x.x.x&gt; &lt;h:h:h:h:h:h:h:h&gt;</list>
</completionHelp>
</properties>
- <command>/usr/bin/telnet $3 $5</command>
+ <command>/usr/bin/telnet $4</command>
+ <children>
+ <tagNode name="port">
+ <properties>
+ <help>Telnet to a host:port</help>
+ <completionHelp>
+ <list>&lt;0-65535&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/telnet $4 $6</command>
+ </tagNode>
+ </children>
</tagNode>
</children>
- </tagNode>
+ </node>
</children>
</node>
</interfaceDefinition>
+
diff --git a/op-mode-definitions/wake-on-lan.xml.in b/op-mode-definitions/wake-on-lan.xml.in
index 7119eeb65..d4589c868 100644
--- a/op-mode-definitions/wake-on-lan.xml.in
+++ b/op-mode-definitions/wake-on-lan.xml.in
@@ -1,26 +1,30 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="wake-on-lan">
- <properties>
- <help>Send Wake-On-LAN (WOL) Magic Packet</help>
- </properties>
+ <node name="execute">
<children>
- <tagNode name="interface">
+ <node name="wake-on-lan">
<properties>
- <help>Interface where the station is connected</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
+ <help>Send Wake-On-LAN (WOL) Magic Packet</help>
</properties>
<children>
- <tagNode name="host">
+ <tagNode name="interface">
<properties>
- <help>Station (MAC) address to wake up</help>
+ <help>Interface where the station is connected</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
</properties>
- <command>sudo /usr/sbin/etherwake -i "$3" "$5"</command>
- </tagNode>
+ <children>
+ <tagNode name="host">
+ <properties>
+ <help>Station (MAC) address to wake up</help>
+ </properties>
+ <command>sudo /usr/sbin/etherwake -i "$4" "$6"</command>
+ </tagNode>
+ </children>
+ </tagNode>
</children>
- </tagNode>
+ </node>
</children>
</node>
</interfaceDefinition>
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index e0fe1ddac..cf7c9d543 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -95,7 +95,8 @@ def get_dependency_dict(config: 'Config') -> dict:
setattr(config, 'cached_dependency_dict', d)
return d
-def run_config_mode_script(script: str, config: 'Config'):
+def run_config_mode_script(target: str, config: 'Config'):
+ script = target + '.py'
path = os.path.join(directories['conf_mode'], script)
name = canon_name(script)
mod = load_as_module(name, path)
@@ -109,15 +110,34 @@ def run_config_mode_script(script: str, config: 'Config'):
except (VyOSError, ConfigError) as e:
raise ConfigError(str(e)) from e
+def run_conditionally(target: str, tagnode: str, config: 'Config'):
+ tag_ext = f'_{tagnode}' if tagnode else ''
+ script_name = f'{target}{tag_ext}'
+
+ scripts_called = getattr(config, 'scripts_called', [])
+ commit_scripts = getattr(config, 'commit_scripts', [])
+
+ debug_print(f'scripts_called: {scripts_called}')
+ debug_print(f'commit_scripts: {commit_scripts}')
+
+ if script_name in commit_scripts and script_name not in scripts_called:
+ debug_print(f'dependency {script_name} deferred to priority')
+ return
+
+ run_config_mode_script(target, config)
+
def def_closure(target: str, config: 'Config',
tagnode: typing.Optional[str] = None) -> typing.Callable:
- script = target + '.py'
def func_impl():
+ tag_value = ''
if tagnode is not None:
os.environ['VYOS_TAGNODE_VALUE'] = tagnode
- run_config_mode_script(script, config)
+ tag_value = tagnode
+ run_conditionally(target, tag_value, config)
+
tag_ext = f'_{tagnode}' if tagnode is not None else ''
func_impl.__name__ = f'{target}{tag_ext}'
+
return func_impl
def set_dependents(case: str, config: 'Config',
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
index f975df45d..b6d4a5558 100644
--- a/python/vyos/configdiff.py
+++ b/python/vyos/configdiff.py
@@ -15,6 +15,7 @@
from enum import IntFlag
from enum import auto
+from itertools import chain
from vyos.config import Config
from vyos.configtree import DiffTree
@@ -22,7 +23,10 @@ from vyos.configdict import dict_merge
from vyos.utils.dict import get_sub_dict
from vyos.utils.dict import mangle_dict_keys
from vyos.utils.dict import dict_search_args
+from vyos.utils.dict import dict_to_key_paths
from vyos.xml_ref import get_defaults
+from vyos.xml_ref import owner
+from vyos.xml_ref import priority
class ConfigDiffError(Exception):
"""
@@ -94,6 +98,39 @@ def get_config_diff(config, key_mangling=None):
return ConfigDiff(config, key_mangling, diff_tree=diff_t,
diff_dict=diff_d)
+def get_commit_scripts(config) -> list:
+ """Return the list of config scripts to be executed by commit
+
+ Return a list of the scripts to be called by commit for the proposed
+ config. The list is ordered by priority for reference, however, the
+ actual order of execution by the commit algorithm is not reflected
+ (delete vs. add queue), nor needed for current use.
+ """
+ if not config or not isinstance(config, Config):
+ raise TypeError("argument must me a Config instance")
+
+ if hasattr(config, 'commit_scripts'):
+ return getattr(config, 'commit_scripts')
+
+ D = get_config_diff(config)
+ d = D._diff_dict
+ s = set()
+ for p in chain(dict_to_key_paths(d['sub']), dict_to_key_paths(d['add'])):
+ p_owner = owner(p, with_tag=True)
+ if not p_owner:
+ continue
+ p_priority = priority(p)
+ if not p_priority:
+ # default priority in legacy commit-algorithm
+ p_priority = 0
+ p_priority = int(p_priority)
+ s.add((p_priority, p_owner))
+
+ res = [x[1] for x in sorted(s, key=lambda x: x[0])]
+ setattr(config, 'commit_scripts', res)
+
+ return res
+
class ConfigDiff(object):
"""
The class of config changes as represented by comparison between the
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index f0cf3c924..64fed8177 100755
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -151,6 +151,20 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
proto = '{tcp, udp}'
output.append(f'meta l4proto {operator} {proto}')
+ if 'ethernet_type' in rule_conf:
+ ether_type_mapping = {
+ '802.1q': '8021q',
+ '802.1ad': '8021ad',
+ 'ipv6': 'ip6',
+ 'ipv4': 'ip',
+ 'arp': 'arp'
+ }
+ ether_type = rule_conf['ethernet_type']
+ operator = '!=' if ether_type.startswith('!') else ''
+ ether_type = ether_type.lstrip('!')
+ ether_type = ether_type_mapping.get(ether_type, ether_type)
+ output.append(f'ether type {operator} {ether_type}')
+
for side in ['destination', 'source']:
if side in rule_conf:
prefix = side[0]
@@ -482,6 +496,19 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
output.append(f'vlan id {rule_conf["vlan"]["id"]}')
if 'priority' in rule_conf['vlan']:
output.append(f'vlan pcp {rule_conf["vlan"]["priority"]}')
+ if 'ethernet_type' in rule_conf['vlan']:
+ ether_type_mapping = {
+ '802.1q': '8021q',
+ '802.1ad': '8021ad',
+ 'ipv6': 'ip6',
+ 'ipv4': 'ip',
+ 'arp': 'arp'
+ }
+ ether_type = rule_conf['vlan']['ethernet_type']
+ operator = '!=' if ether_type.startswith('!') else ''
+ ether_type = ether_type.lstrip('!')
+ ether_type = ether_type_mapping.get(ether_type, ether_type)
+ output.append(f'vlan type {operator} {ether_type}')
if 'log' in rule_conf:
action = rule_conf['action'] if 'action' in rule_conf else 'accept'
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index e54548788..5fab3c2a1 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -199,7 +199,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}')
+ if ipv6:
+ output.append(f'{ip_prefix} {prefix}addr {operator} @A6_{group_name}')
+ else:
+ output.append(f'{ip_prefix} {prefix}addr {operator} @A_{group_name}')
# Generate firewall group domain-group
elif 'domain_group' in group and not (ignore_type_addr and target == nat_type):
group_name = group['domain_group']
@@ -214,7 +217,10 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):
if group_name[0] == '!':
operator = '!='
group_name = group_name[1:]
- output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}')
+ if ipv6:
+ output.append(f'{ip_prefix} {prefix}addr {operator} @N6_{group_name}')
+ else:
+ output.append(f'{ip_prefix} {prefix}addr {operator} @N_{group_name}')
if 'mac_group' in group:
group_name = group['mac_group']
operator = ''
diff --git a/python/vyos/utils/convert.py b/python/vyos/utils/convert.py
index 41e65081f..dd4266f57 100644
--- a/python/vyos/utils/convert.py
+++ b/python/vyos/utils/convert.py
@@ -12,41 +12,72 @@
#
# 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/>.
+import re
+
+# Define the number of seconds in each time unit
+time_units = {
+ 'y': 60 * 60 * 24 * 365.25, # year
+ 'w': 60 * 60 * 24 * 7, # week
+ 'd': 60 * 60 * 24, # day
+ 'h': 60 * 60, # hour
+ 'm': 60, # minute
+ 's': 1 # second
+}
+
+
+def human_to_seconds(time_str):
+ """ Converts a human-readable interval such as 1w4d18h35m59s
+ to number of seconds
+ """
+
+ time_patterns = {
+ 'y': r'(\d+)\s*y',
+ 'w': r'(\d+)\s*w',
+ 'd': r'(\d+)\s*d',
+ 'h': r'(\d+)\s*h',
+ 'm': r'(\d+)\s*m',
+ 's': r'(\d+)\s*s'
+ }
+
+ total_seconds = 0
+
+ for unit, pattern in time_patterns.items():
+ match = re.search(pattern, time_str)
+ if match:
+ value = int(match.group(1))
+ total_seconds += value * time_units[unit]
+
+ return int(total_seconds)
+
def seconds_to_human(s, separator=""):
""" Converts number of seconds passed to a human-readable
interval such as 1w4d18h35m59s
"""
s = int(s)
-
- year = 60 * 60 * 24 * 365.25
- week = 60 * 60 * 24 * 7
- day = 60 * 60 * 24
- hour = 60 * 60
-
result = []
- years = s // year
+ years = s // time_units['y']
if years > 0:
result.append(f'{int(years)}y')
- s = int(s % year)
+ s = int(s % time_units['y'])
- weeks = s // week
+ weeks = s // time_units['w']
if weeks > 0:
result.append(f'{weeks}w')
- s = s % week
+ s = s % time_units['w']
- days = s // day
+ days = s // time_units['d']
if days > 0:
result.append(f'{days}d')
- s = s % day
+ s = s % time_units['d']
- hours = s // hour
+ hours = s // time_units['h']
if hours > 0:
result.append(f'{hours}h')
- s = s % hour
+ s = s % time_units['h']
- minutes = s // 60
+ minutes = s // time_units['m']
if minutes > 0:
result.append(f'{minutes}m')
s = s % 60
@@ -57,6 +88,7 @@ def seconds_to_human(s, separator=""):
return separator.join(result)
+
def bytes_to_human(bytes, initial_exponent=0, precision=2,
int_below_exponent=0):
""" Converts a value in bytes to a human-readable size string like 640 KB
diff --git a/python/vyos/utils/dict.py b/python/vyos/utils/dict.py
index 1eb6abcd5..1a7a6b96f 100644
--- a/python/vyos/utils/dict.py
+++ b/python/vyos/utils/dict.py
@@ -267,6 +267,7 @@ def dict_to_paths_values(conf: dict) -> dict:
dict_of_options[path] = dict_search(path,conf)
return dict_of_options
+
def dict_to_key_paths(d: dict) -> list:
""" Generator to return list of key paths from dict of list[str]|str
"""
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index 91ce394f7..99d8432d2 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# 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
@@ -54,8 +54,8 @@ def is_valueless(path: list) -> bool:
def is_leaf(path: list) -> bool:
return load_reference().is_leaf(path)
-def owner(path: list) -> str:
- return load_reference().owner(path)
+def owner(path: list, with_tag=False) -> str:
+ return load_reference().owner(path, with_tag=with_tag)
def priority(path: list) -> str:
return load_reference().priority(path)
diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py
index c85835ffd..5ff28daed 100644
--- a/python/vyos/xml_ref/definition.py
+++ b/python/vyos/xml_ref/definition.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# 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
@@ -139,28 +139,38 @@ class Xml:
ref_path = path.copy()
d = self.ref
data = ''
+ tag = ''
while ref_path and d:
+ tag_val = ''
d = d.get(ref_path[0], {})
ref_path.pop(0)
if self._is_tag_node(d) and ref_path:
+ tag_val = ref_path[0]
ref_path.pop(0)
if self._is_leaf_node(d) and ref_path:
ref_path.pop(0)
res = self._get_ref_node_data(d, name)
if res is not None:
data = res
+ tag = tag_val
- return data
+ return data, tag
- def owner(self, path: list) -> str:
+ def owner(self, path: list, with_tag=False) -> str:
from pathlib import Path
- data = self._least_upper_data(path, 'owner')
+ data, tag = self._least_upper_data(path, 'owner')
+ tag_ext = f'_{tag}' if tag else ''
if data:
- data = Path(data.split()[0]).name
+ if with_tag:
+ data = Path(data.split()[0]).stem
+ data = f'{data}{tag_ext}'
+ else:
+ data = Path(data.split()[0]).name
return data
def priority(self, path: list) -> str:
- return self._least_upper_data(path, 'priority')
+ data, _ = self._least_upper_data(path, 'priority')
+ return data
@staticmethod
def _dict_get(d: dict, path: list) -> dict:
diff --git a/smoketest/scripts/cli/test_config_dependency.py b/smoketest/scripts/cli/test_config_dependency.py
index 14e88321a..99e807ac5 100755
--- a/smoketest/scripts/cli/test_config_dependency.py
+++ b/smoketest/scripts/cli/test_config_dependency.py
@@ -16,13 +16,39 @@
import unittest
+from time import sleep
-from base_vyostest_shim import VyOSUnitTestSHIM
-
+from vyos.utils.process import is_systemd_service_running
+from vyos.utils.process import cmd
from vyos.configsession import ConfigSessionError
+from base_vyostest_shim import VyOSUnitTestSHIM
+
class TestConfigDep(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # smoketests are run without configd in 1.4; with configd in 1.5
+ # the tests below check behavior under configd:
+ # test_configdep_error checks for regression under configd (T6559)
+ # test_configdep_prio_queue checks resolution under configd (T6671)
+ cls.running_state = is_systemd_service_running('vyos-configd.service')
+
+ if not cls.running_state:
+ cmd('sudo systemctl start vyos-configd.service')
+ # allow time for init
+ sleep(1)
+
+ super(TestConfigDep, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestConfigDep, cls).tearDownClass()
+
+ # return to running_state
+ if not cls.running_state:
+ cmd('sudo systemctl stop vyos-configd.service')
+
def test_configdep_error(self):
address_group = 'AG'
address = '192.168.137.5'
@@ -45,5 +71,60 @@ class TestConfigDep(VyOSUnitTestSHIM.TestCase):
self.cli_delete(['nat'])
self.cli_commit()
+ def test_configdep_prio_queue(self):
+ # confirm that that a dependency (in this case, conntrack ->
+ # conntrack-sync) is not immediately called if the target is
+ # scheduled in the priority queue, indicating that it may require an
+ # intermediate activitation (bond0)
+ bonding_base = ['interfaces', 'bonding']
+ bond_interface = 'bond0'
+ bond_address = '192.0.2.1/24'
+ vrrp_group_base = ['high-availability', 'vrrp', 'group']
+ vrrp_sync_group_base = ['high-availability', 'vrrp', 'sync-group']
+ vrrp_group = 'ETH2'
+ vrrp_sync_group = 'GROUP'
+ conntrack_sync_base = ['service', 'conntrack-sync']
+ conntrack_peer = '192.0.2.77'
+
+ # simple set to trigger in-session conntrack -> conntrack-sync
+ # dependency; note that this is triggered on boot in 1.4 due to
+ # default 'system conntrack modules'
+ self.cli_set(['system', 'conntrack', 'table-size', '524288'])
+
+ self.cli_set(['interfaces', 'ethernet', 'eth2', 'address',
+ '198.51.100.2/24'])
+
+ self.cli_set(bonding_base + [bond_interface, 'address',
+ bond_address])
+ self.cli_set(bonding_base + [bond_interface, 'member', 'interface',
+ 'eth3'])
+
+ self.cli_set(vrrp_group_base + [vrrp_group, 'address',
+ '198.51.100.200/24'])
+ self.cli_set(vrrp_group_base + [vrrp_group, 'hello-source-address',
+ '198.51.100.2'])
+ self.cli_set(vrrp_group_base + [vrrp_group, 'interface', 'eth2'])
+ self.cli_set(vrrp_group_base + [vrrp_group, 'priority', '200'])
+ self.cli_set(vrrp_group_base + [vrrp_group, 'vrid', '22'])
+ self.cli_set(vrrp_sync_group_base + [vrrp_sync_group, 'member',
+ vrrp_group])
+
+ self.cli_set(conntrack_sync_base + ['failover-mechanism', 'vrrp',
+ 'sync-group', vrrp_sync_group])
+
+ self.cli_set(conntrack_sync_base + ['interface', bond_interface,
+ 'peer', conntrack_peer])
+
+ self.cli_commit()
+
+ # clean up
+ self.cli_delete(bonding_base)
+ self.cli_delete(vrrp_group_base)
+ self.cli_delete(vrrp_sync_group_base)
+ self.cli_delete(conntrack_sync_base)
+ self.cli_delete(['interfaces', 'ethernet', 'eth2', 'address'])
+ self.cli_delete(['system', 'conntrack', 'table-size'])
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 3dd97a175..c03b9eb44 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -208,6 +208,22 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.assertEqual(c['NetworkSettings']['Networks'][net_name]['Gateway'] , str(ip_interface(prefix4).ip + 1))
self.assertEqual(c['NetworkSettings']['Networks'][net_name]['IPAddress'] , str(ip_interface(prefix4).ip + ii))
+ def test_no_name_server(self):
+ prefix = '192.0.2.0/24'
+ base_name = 'ipv4'
+ net_name = 'NET01'
+
+ self.cli_set(base_path + ['network', net_name, 'prefix', prefix])
+ self.cli_set(base_path + ['network', net_name, 'no-name-server'])
+
+ name = f'{base_name}-2'
+ self.cli_set(base_path + ['name', name, 'image', cont_image])
+ self.cli_set(base_path + ['name', name, 'network', net_name, 'address', str(ip_interface(prefix).ip + 2)])
+ self.cli_commit()
+
+ n = cmd_to_json(f'sudo podman network inspect {net_name}')
+ self.assertEqual(n['dns_enabled'], False)
+
def test_uid_gid(self):
cont_name = 'uid-test'
gid = '100'
@@ -230,5 +246,23 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
tmp = cmd(f'sudo podman exec -it {cont_name} id -g')
self.assertEqual(tmp, gid)
+ def test_api_socket(self):
+ base_name = 'api-test'
+ container_list = range(1, 5)
+
+ for ii in container_list:
+ name = f'{base_name}-{ii}'
+ self.cli_set(base_path + ['name', name, 'image', cont_image])
+ self.cli_set(base_path + ['name', name, 'allow-host-networks'])
+
+ self.cli_commit()
+
+ # Query API about running containers
+ tmp = cmd("sudo curl --unix-socket /run/podman/podman.sock -H 'content-type: application/json' -sf http://localhost/containers/json")
+ tmp = json.loads(tmp)
+
+ # We expect the same amount of containers from the API that we started above
+ self.assertEqual(len(container_list), len(tmp))
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py
index b8031eed0..3e9ec2935 100755
--- a/smoketest/scripts/cli/test_firewall.py
+++ b/smoketest/scripts/cli/test_firewall.py
@@ -707,6 +707,7 @@ 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', 'bridge', 'name', name, 'default-action', 'accept'])
self.cli_set(['firewall', 'bridge', 'name', name, 'default-log'])
@@ -720,6 +721,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'default-log'])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'action', 'accept'])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'id', vlan_id])
+ self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '1', 'vlan', 'ethernet-type', 'ipv4'])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'action', 'jump'])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'jump-target', name])
self.cli_set(['firewall', 'bridge', 'forward', 'filter', 'rule', '2', 'vlan', 'priority', vlan_prior])
@@ -731,6 +733,9 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'action', 'notrack'])
self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '1', 'destination', 'group', 'ipv6-address-group', 'AGV6'])
+ self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '2', 'ethernet-type', 'arp'])
+ self.cli_set(['firewall', 'bridge', 'prerouting', 'filter', 'rule', '2', 'action', 'accept'])
+
self.cli_commit()
@@ -741,7 +746,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain VYOS_FORWARD_filter'],
['type filter hook forward priority filter; policy accept;'],
['jump VYOS_STATE_POLICY'],
- [f'vlan id {vlan_id}', 'accept'],
+ [f'vlan id {vlan_id}', 'vlan type ip', 'accept'],
[f'vlan pcp {vlan_prior}', f'jump NAME_{name}'],
['log prefix "[bri-FWD-filter-default-D]"', 'drop', 'FWD-filter default-action drop'],
[f'chain NAME_{name}'],
@@ -750,9 +755,14 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase):
['chain VYOS_INPUT_filter'],
['type filter hook input priority filter; policy accept;'],
['ct state new', 'ip saddr 192.0.2.2', f'iifname "{interface_in}"', 'accept'],
+ ['chain VYOS_OUTPUT_filter'],
+ ['type filter hook output priority filter; policy accept;'],
+ ['ct state invalid', 'udp sport 67', 'udp dport 68', 'accept'],
+ ['ct state invalid', 'ether type arp', 'accept'],
['chain VYOS_PREROUTING_filter'],
['type filter hook prerouting priority filter; policy accept;'],
- ['ip6 daddr @A6_AGV6', 'notrack']
+ ['ip6 daddr @A6_AGV6', 'notrack'],
+ ['ether type arp', 'accept']
]
self.verify_nftables(nftables_search, 'bridge vyos_filter')
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 7bfe0d221..b8b18f30f 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -300,7 +300,89 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
for key, value in vht_opt.items():
self.assertIn(value, tmp)
- def test_wireless_hostapd_he_config(self):
+ def test_wireless_hostapd_he_2ghz_config(self):
+ # Only set the hostapd (access-point) options - HE mode for 802.11ax at 2.4GHz
+ interface = self._interfaces[1] # wlan1
+ ssid = 'ssid'
+ channel = '1'
+ sae_pw = 'VyOSVyOSVyOS'
+ bss_color = '13'
+ channel_set_width = '81'
+
+ self.cli_set(self._base_path + [interface, 'ssid', ssid])
+ self.cli_set(self._base_path + [interface, 'type', 'access-point'])
+ self.cli_set(self._base_path + [interface, 'channel', channel])
+ self.cli_set(self._base_path + [interface, 'mode', 'ax'])
+ self.cli_set(self._base_path + [interface, 'security', 'wpa', 'mode', 'wpa2'])
+ self.cli_set(self._base_path + [interface, 'security', 'wpa', 'passphrase', sae_pw])
+ self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'CCMP'])
+ self.cli_set(self._base_path + [interface, 'security', 'wpa', 'cipher', 'GCMP'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', '40mhz-incapable'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht20'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht40+'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'channel-set-width', 'ht40-'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'short-gi', '20'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'ht', 'short-gi', '40'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'bss-color', bss_color])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'channel-set-width', channel_set_width])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'multi-user-beamformer'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformer'])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformee'])
+
+ self.cli_commit()
+
+ #
+ # Validate Config
+ #
+ tmp = get_config_value(interface, 'interface')
+ self.assertEqual(interface, tmp)
+
+ # ssid
+ tmp = get_config_value(interface, 'ssid')
+ self.assertEqual(ssid, tmp)
+
+ # mode of operation resulting from [interface, 'mode', 'ax']
+ tmp = get_config_value(interface, 'hw_mode')
+ self.assertEqual('g', tmp)
+ tmp = get_config_value(interface, 'ieee80211h')
+ self.assertEqual('1', tmp)
+ tmp = get_config_value(interface, 'ieee80211ax')
+ self.assertEqual('1', tmp)
+
+ # channel and channel width
+ tmp = get_config_value(interface, 'channel')
+ self.assertEqual(channel, tmp)
+ tmp = get_config_value(interface, 'op_class')
+ self.assertEqual(channel_set_width, tmp)
+
+ # BSS coloring
+ tmp = get_config_value(interface, 'he_bss_color')
+ self.assertEqual(bss_color, tmp)
+
+ # sae_password
+ tmp = get_config_value(interface, 'wpa_passphrase')
+ self.assertEqual(sae_pw, tmp)
+
+ # WPA3 and dependencies
+ tmp = get_config_value(interface, 'wpa')
+ self.assertEqual('2', tmp)
+ tmp = get_config_value(interface, 'rsn_pairwise')
+ self.assertEqual('CCMP GCMP', tmp)
+ tmp = get_config_value(interface, 'wpa_key_mgmt')
+ self.assertEqual('WPA-PSK WPA-PSK-SHA256', tmp)
+
+ # beamforming
+ tmp = get_config_value(interface, 'he_mu_beamformer')
+ self.assertEqual('1', tmp)
+ tmp = get_config_value(interface, 'he_su_beamformee')
+ self.assertEqual('1', tmp)
+ tmp = get_config_value(interface, 'he_mu_beamformer')
+ self.assertEqual('1', tmp)
+
+ # Check for running process
+ self.assertTrue(process_named_running('hostapd'))
+
+ def test_wireless_hostapd_he_6ghz_config(self):
# Only set the hostapd (access-point) options - HE mode for 802.11ax at 6GHz
interface = self._interfaces[1] # wlan1
ssid = 'ssid'
@@ -323,6 +405,7 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'bss-color', bss_color])
self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'channel-set-width', channel_set_width])
self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'center-channel-freq', 'freq-1', center_channel_freq_1])
+ self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'antenna-pattern-fixed'])
self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'multi-user-beamformer'])
self.cli_set(self._base_path + [interface, 'capabilities', 'he', 'beamform', 'single-user-beamformer'])
@@ -370,6 +453,10 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_config_value(interface, 'wpa_key_mgmt')
self.assertEqual('SAE', tmp)
+ # antenna pattern
+ tmp = get_config_value(interface, 'he_6ghz_rx_ant_pat')
+ self.assertEqual('1', tmp)
+
# beamforming
tmp = get_config_value(interface, 'he_mu_beamformer')
self.assertEqual('1', tmp)
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index e8eeae26f..52ad8e3ef 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -141,6 +141,36 @@ class TestNAT66(VyOSUnitTestSHIM.TestCase):
self.verify_nftables(nftables_search, 'ip6 vyos_nat')
+ def test_destination_nat66_network_group(self):
+ address_group = 'smoketest_addr'
+ address_group_member = 'fc00::1'
+ network_group = 'smoketest_net'
+ network_group_member = 'fc00::/64'
+ translation_prefix = 'fc01::/64'
+
+ self.cli_set(['firewall', 'group', 'ipv6-address-group', address_group, 'address', address_group_member])
+ self.cli_set(['firewall', 'group', 'ipv6-network-group', network_group, 'network', network_group_member])
+
+ self.cli_set(dst_path + ['rule', '1', 'destination', 'group', 'address-group', address_group])
+ self.cli_set(dst_path + ['rule', '1', 'translation', 'address', translation_prefix])
+
+ self.cli_set(dst_path + ['rule', '2', 'destination', 'group', 'network-group', network_group])
+ self.cli_set(dst_path + ['rule', '2', 'translation', 'address', translation_prefix])
+
+ self.cli_commit()
+
+ nftables_search = [
+ [f'set A6_{address_group}'],
+ [f'elements = {{ {address_group_member} }}'],
+ [f'set N6_{network_group}'],
+ [f'elements = {{ {network_group_member} }}'],
+ ['ip6 daddr', f'@A6_{address_group}', 'dnat prefix to fc01::/64'],
+ ['ip6 daddr', f'@N6_{network_group}', 'dnat prefix to fc01::/64']
+ ]
+
+ self.verify_nftables(nftables_search, 'ip6 vyos_nat')
+
+
def test_destination_nat66_without_translation_address(self):
self.cli_set(dst_path + ['rule', '1', 'inbound-interface', 'name', 'eth1'])
self.cli_set(dst_path + ['rule', '1', 'destination', 'port', '443'])
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 4db1d7495..9a3f4933e 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -26,6 +26,7 @@ from vyos.utils.process import process_named_running
PDNS_REC_RUN_DIR = '/run/pdns-recursor'
CONFIG_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf'
+PDNS_REC_LUA_CONF_FILE = f'{PDNS_REC_RUN_DIR}/recursor.conf.lua'
FORWARD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.forward-zones.conf'
HOSTSD_FILE = f'{PDNS_REC_RUN_DIR}/recursor.vyos-hostsd.conf.lua'
PROCESS_NAME= 'pdns_recursor'
@@ -300,6 +301,44 @@ class TestServicePowerDNS(VyOSUnitTestSHIM.TestCase):
self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns1\.{test_zone}\.')
self.assertRegex(zone_config, fr'test\s+\d+\s+NS\s+ns2\.{test_zone}\.')
+ def test_zone_cache_url(self):
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
+ self.cli_commit()
+
+ lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
+ self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config)
+
+ def test_zone_cache_axfr(self):
+
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'axfr', '127.0.0.1'])
+ self.cli_commit()
+
+ lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
+ self.assertIn('zoneToCache("smoketest", "axfr", "127.0.0.1", { dnssec = "validate", zonemd = "validate", maxReceivedMBytes = 0, retryOnErrorPeriod = 60, refreshPeriod = 86400, timeout = 20 })', lua_config)
+
+ def test_zone_cache_options(self):
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'dnssec', 'ignore'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'max-zone-size', '100'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'refresh', 'interval', '10'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'retry-interval', '90'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'timeout', '50'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'options', 'zonemd', 'require'])
+ self.cli_commit()
+
+ lua_config = read_file(PDNS_REC_LUA_CONF_FILE)
+ self.assertIn('zoneToCache("smoketest", "url", "https://www.internic.net/domain/root.zone", { dnssec = "ignore", maxReceivedMBytes = 100, refreshPeriod = 10, retryOnErrorPeriod = 90, timeout = 50, zonemd = "require" })', lua_config)
+
+ def test_zone_cache_wrong_source(self):
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'url', 'https://www.internic.net/domain/root.zone'])
+ self.cli_set(base_path + ['zone-cache', 'smoketest', 'source', 'axfr', '127.0.0.1'])
+
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ # correct config to correct finish the test
+ self.cli_delete(base_path + ['zone-cache', 'smoketest', 'source', 'axfr'])
+ self.cli_commit()
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 8add5ee6c..8cd87e0f2 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -195,6 +195,22 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
config = read_file(self._config_file)
self.assertIn('any-login=1', config)
+ def test_pppoe_server_accept_service(self):
+ services = ['user1-service', 'user2-service']
+ self.basic_config()
+
+ for service in services:
+ self.set(['service-name', service])
+ self.set(['accept-any-service'])
+ self.set(['accept-blank-service'])
+ self.cli_commit()
+
+ # Validate configuration values
+ config = read_file(self._config_file)
+ self.assertIn(f'service-name={",".join(services)}', config)
+ self.assertIn('accept-any-service=1', config)
+ self.assertIn('accept-blank-service=1', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index d1ff25a58..6dbb6add4 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -224,5 +224,34 @@ class TestServiceRADVD(VyOSUnitTestSHIM.TestCase):
self.assertIn(tmp, config)
self.assertIn('AdvValidLifetime 65528;', config) # default
+ def test_advsendadvert_advintervalopt(self):
+ ra_src = ['fe80::1', 'fe80::2']
+
+ self.cli_set(base_path + ['prefix', prefix])
+ self.cli_set(base_path + ['no-send-advert'])
+ # commit changes
+ self.cli_commit()
+
+ # Verify generated configuration
+ config = read_file(RADVD_CONF)
+ tmp = get_config_value('AdvSendAdvert')
+ self.assertEqual(tmp, 'off')
+
+ tmp = get_config_value('AdvIntervalOpt')
+ self.assertEqual(tmp, 'on')
+
+ self.cli_set(base_path + ['no-send-interval'])
+ # commit changes
+ self.cli_commit()
+
+ # Verify generated configuration
+ config = read_file(RADVD_CONF)
+ tmp = get_config_value('AdvSendAdvert')
+ self.assertEqual(tmp, 'off')
+
+ tmp = get_config_value('AdvIntervalOpt')
+ self.assertEqual(tmp, 'off')
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_option.py b/smoketest/scripts/cli/test_system_option.py
index c6f48bfc6..ffb1d76ae 100755
--- a/smoketest/scripts/cli/test_system_option.py
+++ b/smoketest/scripts/cli/test_system_option.py
@@ -80,5 +80,20 @@ class TestSystemOption(VyOSUnitTestSHIM.TestCase):
self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh2'), gc_thresh2)
self.assertEqual(sysctl_read('net.ipv4.neigh.default.gc_thresh3'), gc_thresh3)
+ def test_ssh_client_options(self):
+ loopback = 'lo'
+ ssh_client_opt_file = '/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
+
+ self.cli_set(['system', 'option', 'ssh-client', 'source-interface', loopback])
+ self.cli_commit()
+
+ tmp = read_file(ssh_client_opt_file)
+ self.assertEqual(tmp, f'BindInterface {loopback}')
+
+ self.cli_delete(['system', 'option'])
+ self.cli_commit()
+ self.assertFalse(os.path.exists(ssh_client_opt_file))
+
+
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=True)
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index ded370a7a..14387cbbf 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -421,6 +421,10 @@ def generate(container):
'driver': 'host-local'
}
}
+
+ if 'no_name_server' in network_config:
+ tmp['dns_enabled'] = False
+
for prefix in network_config['prefix']:
net = {'subnet': prefix, 'gateway': inc_ip(prefix, 1)}
tmp['subnets'].append(net)
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index c44320f36..95dfae3a5 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -26,6 +26,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.kernel import check_kmod
from vyos.utils.network import interface_exists
from vyos.utils.process import cmd
+from vyos.utils.process import run
from vyos.template import is_ipv6
from vyos import ConfigError
from vyos import airbag
@@ -48,6 +49,14 @@ def get_config(config=None):
if not conf.exists(base):
nat['deleted'] = ''
+ return nat
+
+ nat['firewall_group'] = conf.get_config_dict(['firewall', 'group'], key_mangling=('-', '_'), get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # Remove dynamic firewall groups if present:
+ if 'dynamic_group' in nat['firewall_group']:
+ del nat['firewall_group']['dynamic_group']
return nat
@@ -99,22 +108,33 @@ def verify(nat):
if not interface_exists(interface_name):
Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!')
+ if 'destination' in config and 'group' in config['destination']:
+ if len({'address_group', 'network_group', 'domain_group'} & set(config['destination']['group'])) > 1:
+ raise ConfigError('Only one address-group, network-group or domain-group can be specified')
+
return None
def generate(nat):
if not os.path.exists(nftables_nat66_config):
nat['first_install'] = True
- render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat, permission=0o755)
+ render(nftables_nat66_config, 'firewall/nftables-nat66.j2', nat)
+
+ # dry-run newly generated configuration
+ tmp = run(f'nft --check --file {nftables_nat66_config}')
+ if tmp > 0:
+ raise ConfigError('Configuration file errors encountered!')
+
return None
def apply(nat):
- if not nat:
- return None
-
check_kmod(k_mod)
cmd(f'nft --file {nftables_nat66_config}')
+
+ if not nat or 'deleted' in nat:
+ os.unlink(nftables_nat66_config)
+
call_dependents()
return None
diff --git a/src/conf_mode/service_dns_forwarding.py b/src/conf_mode/service_dns_forwarding.py
index 70686534f..e3bdbc9f8 100755
--- a/src/conf_mode/service_dns_forwarding.py
+++ b/src/conf_mode/service_dns_forwarding.py
@@ -224,6 +224,18 @@ def get_config(config=None):
dns['authoritative_zones'].append(zone)
+ if 'zone_cache' in dns:
+ # convert refresh interval to sec:
+ for _, zone_conf in dns['zone_cache'].items():
+ if 'options' in zone_conf \
+ and 'refresh' in zone_conf['options']:
+
+ if 'on_reload' in zone_conf['options']['refresh']:
+ interval = 0
+ else:
+ interval = zone_conf['options']['refresh']['interval']
+ zone_conf['options']['refresh']['interval'] = interval
+
return dns
def verify(dns):
@@ -259,8 +271,16 @@ def verify(dns):
if not 'system_name_server' in dns:
print('Warning: No "system name-server" configured')
+ if 'zone_cache' in dns:
+ for name, conf in dns['zone_cache'].items():
+ if ('source' not in conf) \
+ or ('url' in conf['source'] and 'axfr' in conf['source']):
+ raise ConfigError(f'Invalid configuration for zone "{name}": '
+ f'Please select one source type "url" or "axfr".')
+
return None
+
def generate(dns):
# bail out early - looks like removal from running config
if not dns:
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index d1647e3a1..52d0b7cda 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -85,6 +85,8 @@ def verify(options):
raise ConfigError('No interface with address "{address}" configured!')
if 'source_interface' in config:
+ # verify_source_interface reuires key 'ifname'
+ config['ifname'] = config['source_interface']
verify_source_interface(config)
if 'source_address' in config:
address = config['source_address']
diff --git a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
index 5d879471d..2a1c5a7b2 100644
--- a/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
+++ b/src/etc/dhcp/dhclient-enter-hooks.d/03-vyos-ipwrapper
@@ -72,6 +72,22 @@ function delroute () {
fi
}
+# try to communicate with vtysh
+function vtysh_conf () {
+ # perform 10 attempts with 1 second delay for retries
+ for i in {1..10} ; do
+ if vtysh -c "conf t" -c "$1" ; then
+ logmsg info "Command was executed successfully via vtysh: \"$1\""
+ return 0
+ else
+ logmsg info "Failed to send command to vtysh, retrying in 1 second"
+ sleep 1
+ fi
+ done
+ logmsg error "Failed to execute command via vtysh after 10 attempts: \"$1\""
+ return 1
+}
+
# replace ip command with this wrapper
function ip () {
# pass comand to system `ip` if this is not related to routes change
@@ -84,7 +100,7 @@ function ip () {
delroute ${@:4}
iptovtysh $@
logmsg info "Sending command to vtysh"
- vtysh -c "conf t" -c "$VTYSH_CMD"
+ vtysh_conf "$VTYSH_CMD"
else
# add ip route to kernel
logmsg info "Modifying routes in kernel: \"/usr/sbin/ip $@\""
diff --git a/src/op_mode/monitor_bandwidth_test.sh b/src/op_mode/execute_bandwidth_test.sh
index a6ad0b42c..a6ad0b42c 100755
--- a/src/op_mode/monitor_bandwidth_test.sh
+++ b/src/op_mode/execute_bandwidth_test.sh
diff --git a/src/op_mode/execute_port-scan.py b/src/op_mode/execute_port-scan.py
new file mode 100644
index 000000000..bf17d0379
--- /dev/null
+++ b/src/op_mode/execute_port-scan.py
@@ -0,0 +1,155 @@
+#! /usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import sys
+
+from vyos.utils.process import call
+
+
+options = {
+ 'port': {
+ 'cmd': '{command} -p {value}',
+ 'type': '<1-65535> <list>',
+ 'help': 'Scan specified ports.'
+ },
+ 'tcp': {
+ 'cmd': '{command} -sT',
+ 'type': 'noarg',
+ 'help': 'Use TCP scan.'
+ },
+ 'udp': {
+ 'cmd': '{command} -sU',
+ 'type': 'noarg',
+ 'help': 'Use UDP scan.'
+ },
+ 'skip-ping': {
+ 'cmd': '{command} -Pn',
+ 'type': 'noarg',
+ 'help': 'Skip the Nmap discovery stage altogether.'
+ },
+ 'ipv6': {
+ 'cmd': '{command} -6',
+ 'type': 'noarg',
+ 'help': 'Enable IPv6 scanning.'
+ },
+}
+
+nmap = 'sudo /usr/bin/nmap'
+
+
+class List(list):
+ def first(self):
+ return self.pop(0) if self else ''
+
+ def last(self):
+ return self.pop() if self else ''
+
+ def prepend(self, value):
+ self.insert(0, value)
+
+
+def completion_failure(option: str) -> None:
+ """
+ Shows failure message after TAB when option is wrong
+ :param option: failure option
+ :type str:
+ """
+ sys.stderr.write('\n\n Invalid option: {}\n\n'.format(option))
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def expansion_failure(option, completions):
+ reason = 'Ambiguous' if completions else 'Invalid'
+ sys.stderr.write(
+ '\n\n {} command: {} [{}]\n\n'.format(reason, ' '.join(sys.argv),
+ option))
+ if completions:
+ sys.stderr.write(' Possible completions:\n ')
+ sys.stderr.write('\n '.join(completions))
+ sys.stderr.write('\n')
+ sys.stdout.write('<nocomps>')
+ sys.exit(1)
+
+
+def complete(prefix):
+ return [o for o in options if o.startswith(prefix)]
+
+
+def convert(command, args):
+ while args:
+ shortname = args.first()
+ longnames = complete(shortname)
+ if len(longnames) != 1:
+ expansion_failure(shortname, longnames)
+ longname = longnames[0]
+ if options[longname]['type'] == 'noarg':
+ command = options[longname]['cmd'].format(
+ command=command, value='')
+ elif not args:
+ sys.exit(f'port-scan: missing argument for {longname} option')
+ else:
+ command = options[longname]['cmd'].format(
+ command=command, value=args.first())
+ return command
+
+
+if __name__ == '__main__':
+ args = List(sys.argv[1:])
+ host = args.first()
+
+ if host == '--get-options-nested':
+ args.first() # pop execute
+ args.first() # pop port-scan
+ args.first() # pop host
+ args.first() # pop <host>
+ usedoptionslist = []
+ while args:
+ option = args.first() # pop option
+ matched = complete(option) # get option parameters
+ usedoptionslist.append(option) # list of used options
+ # Select options
+ if not args:
+ # remove from Possible completions used options
+ for o in usedoptionslist:
+ if o in matched:
+ matched.remove(o)
+ if not matched:
+ sys.stdout.write('<nocomps>')
+ else:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+
+ if len(matched) > 1:
+ sys.stdout.write(' '.join(matched))
+ sys.exit(0)
+ # If option doesn't have value
+ if matched:
+ if options[matched[0]]['type'] == 'noarg':
+ continue
+ else:
+ # Unexpected option
+ completion_failure(option)
+
+ value = args.first() # pop option's value
+ if not args:
+ matched = complete(option)
+ helplines = options[matched[0]]['type']
+ sys.stdout.write(helplines)
+ sys.exit(0)
+
+ command = convert(nmap, args)
+ call(f'{command} -T4 {host}')
diff --git a/src/op_mode/ntp.py b/src/op_mode/ntp.py
index e14cc46d0..6ec0fedcb 100644
--- a/src/op_mode/ntp.py
+++ b/src/op_mode/ntp.py
@@ -110,49 +110,62 @@ def _is_configured():
if not config.exists("service ntp"):
raise vyos.opmode.UnconfiguredSubsystem("NTP service is not enabled.")
+def _extend_command_vrf():
+ config = ConfigTreeQuery()
+ if config.exists('service ntp vrf'):
+ vrf = config.value('service ntp vrf')
+ return f'ip vrf exec {vrf} '
+ return ''
+
+
def show_activity(raw: bool):
_is_configured()
command = f'chronyc'
if raw:
- command += f" -c activity"
- return _get_raw_data(command)
+ command += f" -c activity"
+ return _get_raw_data(command)
else:
- command += f" activity"
- return cmd(command)
+ command = _extend_command_vrf() + command
+ command += f" activity"
+ return cmd(command)
def show_sources(raw: bool):
_is_configured()
command = f'chronyc'
if raw:
- command += f" -c sources"
- return _get_raw_data(command)
+ command += f" -c sources"
+ return _get_raw_data(command)
else:
- command += f" sources -v"
- return cmd(command)
+ command = _extend_command_vrf() + command
+ command += f" sources -v"
+ return cmd(command)
def show_tracking(raw: bool):
_is_configured()
command = f'chronyc'
if raw:
- command += f" -c tracking"
- return _get_raw_data(command)
+ command += f" -c tracking"
+ return _get_raw_data(command)
else:
- command += f" tracking"
- return cmd(command)
+ command = _extend_command_vrf() + command
+ command += f" tracking"
+ return cmd(command)
def show_sourcestats(raw: bool):
_is_configured()
command = f'chronyc'
if raw:
- command += f" -c sourcestats"
- return _get_raw_data(command)
+ command += f" -c sourcestats"
+ return _get_raw_data(command)
else:
- command += f" sourcestats -v"
- return cmd(command)
+ command = _extend_command_vrf() + command
+ command += f" sourcestats -v"
+ return cmd(command)
+
if __name__ == '__main__':
try:
diff --git a/src/op_mode/restart.py b/src/op_mode/restart.py
index 813d3a2b7..a83c8b9d8 100755
--- a/src/op_mode/restart.py
+++ b/src/op_mode/restart.py
@@ -25,11 +25,11 @@ from vyos.utils.commit import commit_in_progress
config = ConfigTreeQuery()
service_map = {
- 'dhcp' : {
+ 'dhcp': {
'systemd_service': 'kea-dhcp4-server',
'path': ['service', 'dhcp-server'],
},
- 'dhcpv6' : {
+ 'dhcpv6': {
'systemd_service': 'kea-dhcp6-server',
'path': ['service', 'dhcpv6-server'],
},
@@ -61,24 +61,40 @@ service_map = {
'systemd_service': 'radvd',
'path': ['service', 'router-advert'],
},
- 'snmp' : {
+ 'snmp': {
'systemd_service': 'snmpd',
},
- 'ssh' : {
+ 'ssh': {
'systemd_service': 'ssh',
},
- 'suricata' : {
+ 'suricata': {
'systemd_service': 'suricata',
},
- 'vrrp' : {
+ 'vrrp': {
'systemd_service': 'keepalived',
'path': ['high-availability', 'vrrp'],
},
- 'webproxy' : {
+ 'webproxy': {
'systemd_service': 'squid',
},
}
-services = typing.Literal['dhcp', 'dhcpv6', 'dns_dynamic', 'dns_forwarding', 'igmp_proxy', 'ipsec', 'mdns_repeater', 'reverse_proxy', 'router_advert', 'snmp', 'ssh', 'suricata' 'vrrp', 'webproxy']
+services = typing.Literal[
+ 'dhcp',
+ 'dhcpv6',
+ 'dns_dynamic',
+ 'dns_forwarding',
+ 'igmp_proxy',
+ 'ipsec',
+ 'mdns_repeater',
+ 'reverse_proxy',
+ 'router_advert',
+ 'snmp',
+ 'ssh',
+ 'suricata',
+ 'vrrp',
+ 'webproxy',
+]
+
def _verify(func):
"""Decorator checks if DHCP(v6) config exists"""
@@ -102,13 +118,18 @@ def _verify(func):
# Check if config does not exist
if not config.exists(path):
- raise vyos.opmode.UnconfiguredSubsystem(f'Service {human_name} is not configured!')
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'Service {human_name} is not configured!'
+ )
if config.exists(path + ['disable']):
- raise vyos.opmode.UnconfiguredSubsystem(f'Service {human_name} is disabled!')
+ raise vyos.opmode.UnconfiguredSubsystem(
+ f'Service {human_name} is disabled!'
+ )
return func(*args, **kwargs)
return _wrapper
+
@_verify
def restart_service(raw: bool, name: services, vrf: typing.Optional[str]):
systemd_service = service_map[name]['systemd_service']
@@ -117,6 +138,7 @@ def restart_service(raw: bool, name: services, vrf: typing.Optional[str]):
else:
call(f'systemctl restart "{systemd_service}.service"')
+
if __name__ == '__main__':
try:
res = vyos.opmode.run(sys.modules[__name__])
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index d797e90cf..3674d9627 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -30,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.configdiff import get_commit_scripts
from vyos.config import Config
from vyos import ConfigError
@@ -220,6 +221,12 @@ def initialization(socket):
dependent_func: dict[str, list[typing.Callable]] = {}
setattr(config, 'dependent_func', dependent_func)
+ commit_scripts = get_commit_scripts(config)
+ logger.debug(f'commit_scripts: {commit_scripts}')
+
+ scripts_called = []
+ setattr(config, 'scripts_called', scripts_called)
+
return config
def process_node_data(config, data, last: bool = False) -> int:
@@ -228,6 +235,7 @@ def process_node_data(config, data, last: bool = False) -> int:
return R_ERROR_DAEMON
script_name = None
+ os.environ['VYOS_TAGNODE_VALUE'] = ''
args = []
config.dependency_list.clear()
@@ -244,6 +252,12 @@ def process_node_data(config, data, last: bool = False) -> int:
args = res.group(3).split()
args.insert(0, f'{script_name}.py')
+ tag_value = os.getenv('VYOS_TAGNODE_VALUE', '')
+ tag_ext = f'_{tag_value}' if tag_value else ''
+ script_record = f'{script_name}{tag_ext}'
+ scripts_called = getattr(config, 'scripts_called', [])
+ scripts_called.append(script_record)
+
if script_name not in include_set:
return R_PASS
@@ -302,11 +316,12 @@ if __name__ == '__main__':
socket.send(resp.encode())
config = initialization(socket)
elif message["type"] == "node":
- 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)
+ if message["last"] and config:
+ scripts_called = getattr(config, 'scripts_called', [])
+ logger.debug(f'scripts_called: {scripts_called}')
else:
logger.critical(f"Unexpected message: {message}")
diff --git a/src/systemd/podman.service b/src/systemd/podman.service
new file mode 100644
index 000000000..20a16304b
--- /dev/null
+++ b/src/systemd/podman.service
@@ -0,0 +1,16 @@
+[Unit]
+Description=Podman API Service
+Requires=podman.socket
+After=podman.socket
+Documentation=man:podman-system-service(1)
+StartLimitIntervalSec=0
+
+[Service]
+Delegate=true
+Type=exec
+KillMode=process
+Environment=LOGGING="--log-level=info"
+ExecStart=/usr/bin/podman $LOGGING system service
+
+[Install]
+WantedBy=default.target
diff --git a/src/systemd/podman.socket b/src/systemd/podman.socket
new file mode 100644
index 000000000..397058ee4
--- /dev/null
+++ b/src/systemd/podman.socket
@@ -0,0 +1,10 @@
+[Unit]
+Description=Podman API Socket
+Documentation=man:podman-system-service(1)
+
+[Socket]
+ListenStream=%t/podman/podman.sock
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target