summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/lint-with-ruff.yml (renamed from .github/workflows/lint-with-darker-ruff.yml)6
-rw-r--r--.github/workflows/package-smoketest.yml46
-rw-r--r--.github/workflows/trigger-rebuild-repo-package.yml32
-rw-r--r--Makefile1
-rw-r--r--data/templates/accel-ppp/pppoe.config.j26
-rw-r--r--data/templates/chrony/chrony.conf.j217
-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/frr/daemons.frr.tmpl2
-rw-r--r--data/templates/frr/fabricd.frr.j272
-rw-r--r--data/templates/router-advert/radvd.conf.j22
-rw-r--r--data/templates/rsyslog/rsyslog.conf.j210
-rw-r--r--data/templates/wifi/hostapd.conf.j212
-rw-r--r--debian/control8
-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/include/isis/protocol-common-config.xml.i20
-rw-r--r--interface-definitions/include/log-adjacency-changes.xml.i8
-rw-r--r--interface-definitions/include/net.xml.i14
-rw-r--r--interface-definitions/include/openfabric/password.xml.i20
-rw-r--r--interface-definitions/interfaces_bonding.xml.in1
-rw-r--r--interface-definitions/interfaces_wireless.xml.in68
-rw-r--r--interface-definitions/nat66.xml.in1
-rw-r--r--interface-definitions/protocols_openfabric.xml.in218
-rw-r--r--interface-definitions/service_dns_forwarding.xml.in173
-rw-r--r--interface-definitions/service_lldp.xml.in4
-rw-r--r--interface-definitions/service_ntp.xml.in80
-rw-r--r--interface-definitions/service_pppoe-server.xml.in12
-rw-r--r--interface-definitions/service_router-advert.xml.in6
-rw-r--r--interface-definitions/system_option.xml.in20
-rw-r--r--interface-definitions/system_syslog.xml.in6
-rw-r--r--op-mode-definitions/date.xml.in2
-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-ssh.xml.in34
-rw-r--r--op-mode-definitions/execute-wamp.xml.in (renamed from op-mode-definitions/force-wamp.xml.in)2
-rwxr-xr-x[-rw-r--r--]op-mode-definitions/firewall.xml.in220
-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/include/show-route-openfabric.xml.i8
-rw-r--r--op-mode-definitions/install-mok.xml.in13
-rw-r--r--op-mode-definitions/monitor-log.xml.in6
-rw-r--r--op-mode-definitions/restart-frr.xml.in6
-rw-r--r--op-mode-definitions/show-interfaces-wireguard.xml.in2
-rw-r--r--op-mode-definitions/show-ip-route.xml.in1
-rw-r--r--op-mode-definitions/show-ipv6-route.xml.in1
-rwxr-xr-x[-rw-r--r--]op-mode-definitions/show-log.xml.in131
-rw-r--r--op-mode-definitions/show-openfabric.xml.in51
-rw-r--r--op-mode-definitions/show-secure-boot.xml.in21
-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/configverify.py17
-rw-r--r--python/vyos/ethtool.py88
-rwxr-xr-xpython/vyos/firewall.py27
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/ifconfig/bond.py3
-rw-r--r--python/vyos/ifconfig/ethernet.py3
-rw-r--r--python/vyos/ifconfig/interface.py69
-rw-r--r--python/vyos/ifconfig/wireguard.py110
-rw-r--r--python/vyos/nat.py10
-rw-r--r--python/vyos/system/grub.py2
-rw-r--r--python/vyos/utils/boot.py6
-rw-r--r--python/vyos/utils/convert.py62
-rw-r--r--python/vyos/utils/system.py8
-rw-r--r--smoketest/scripts/cli/base_interfaces_test.py162
-rwxr-xr-xsmoketest/scripts/cli/test_container.py34
-rwxr-xr-xsmoketest/scripts/cli/test_firewall.py14
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py34
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py142
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py89
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py30
-rw-r--r--smoketest/scripts/cli/test_protocols_openfabric.py186
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py39
-rwxr-xr-xsmoketest/scripts/cli/test_service_ntp.py95
-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_syslog.py33
-rwxr-xr-xsrc/activation-scripts/20-ethernet_offload.py13
-rwxr-xr-xsrc/conf_mode/container.py4
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py4
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py40
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py78
-rwxr-xr-xsrc/conf_mode/nat66.py28
-rwxr-xr-xsrc/conf_mode/policy.py8
-rw-r--r--src/conf_mode/protocols_openfabric.py145
-rwxr-xr-xsrc/conf_mode/service_dns_forwarding.py20
-rwxr-xr-xsrc/conf_mode/service_ntp.py20
-rwxr-xr-xsrc/conf_mode/system_option.py45
-rwxr-xr-xsrc/conf_mode/system_syslog.py20
-rw-r--r--src/etc/sudoers.d/vyos3
-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/interfaces_wireguard.py53
-rwxr-xr-xsrc/op_mode/restart.py42
-rwxr-xr-xsrc/op_mode/restart_frr.py2
-rwxr-xr-xsrc/op_mode/secure_boot.py50
-rwxr-xr-xsrc/op_mode/version.py9
-rwxr-xr-xsrc/op_mode/vpn_ike_sa.py2
-rwxr-xr-xsrc/services/vyos-configd189
-rwxr-xr-xsrc/services/vyos-http-api-server4
-rw-r--r--src/shim/vyshim.c45
-rw-r--r--src/systemd/podman.service16
-rw-r--r--src/systemd/podman.socket10
-rwxr-xr-xsrc/validators/interface-address2
-rwxr-xr-xsrc/validators/ip-address4
-rwxr-xr-xsrc/validators/ip-cidr4
-rwxr-xr-xsrc/validators/ip-host4
-rwxr-xr-xsrc/validators/ip-prefix4
-rwxr-xr-xsrc/validators/ipv44
-rwxr-xr-xsrc/validators/ipv4-address4
-rwxr-xr-xsrc/validators/ipv4-host4
-rwxr-xr-xsrc/validators/ipv4-multicast4
-rwxr-xr-xsrc/validators/ipv4-prefix4
-rwxr-xr-xsrc/validators/ipv64
-rwxr-xr-xsrc/validators/ipv6-address4
-rwxr-xr-xsrc/validators/ipv6-host4
-rwxr-xr-xsrc/validators/ipv6-multicast4
-rwxr-xr-xsrc/validators/ipv6-prefix4
124 files changed, 3362 insertions, 652 deletions
diff --git a/.github/workflows/lint-with-darker-ruff.yml b/.github/workflows/lint-with-ruff.yml
index 01f7cd448..00cc9ca1b 100644
--- a/.github/workflows/lint-with-darker-ruff.yml
+++ b/.github/workflows/lint-with-ruff.yml
@@ -1,4 +1,4 @@
-name: Lint py code with darker and ruff
+name: Lint py code with ruff
on:
pull_request_target:
branches:
@@ -9,6 +9,6 @@ permissions:
contents: read
jobs:
- darker-ruff-lint:
- uses: vyos/.github/.github/workflows/lint-with-darker-ruff.yml@current
+ ruff-lint:
+ uses: vyos/.github/.github/workflows/lint-with-ruff.yml@current
secrets: inherit
diff --git a/.github/workflows/package-smoketest.yml b/.github/workflows/package-smoketest.yml
index 467ff062e..289ad70f3 100644
--- a/.github/workflows/package-smoketest.yml
+++ b/.github/workflows/package-smoketest.yml
@@ -1,7 +1,7 @@
name: VyOS ISO integration Test
on:
- pull_request:
+ pull_request_target:
branches:
- current
paths:
@@ -15,6 +15,9 @@ permissions:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for PR comments
+ BUILD_BY: autobuild@vyos.net
+ DEBIAN_MIRROR: http://deb.debian.org/debian/
+ VYOS_MIRROR: https://rolling-packages.vyos.net/current/
jobs:
build_iso:
@@ -23,9 +26,6 @@ jobs:
container:
image: vyos/vyos-build:current
options: --sysctl net.ipv6.conf.lo.disable_ipv6=0 --privileged
- env:
- BUILD_BY: autobuild@vyos.net
- DEBIAN_MIRROR: http://deb.debian.org/debian/
outputs:
build_version: ${{ steps.version.outputs.build_version }}
steps:
@@ -39,6 +39,7 @@ jobs:
path: packages/vyos-1x
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Build vyos-1x package
run: |
cd packages/vyos-1x; dpkg-buildpackage -uc -us -tc -b
@@ -52,9 +53,11 @@ jobs:
sudo --preserve-env ./build-vyos-image \
--architecture amd64 \
--build-by $BUILD_BY \
+ --build-type release \
+ --custom-package vyos-1x-smoketest \
--debian-mirror $DEBIAN_MIRROR \
--version ${{ steps.version.outputs.build_version }} \
- --build-type release \
+ --vyos-mirror $VYOS_MIRROR \
generic
- uses: actions/upload-artifact@v4
with:
@@ -154,11 +157,43 @@ jobs:
echo "exit_code=fail" >> $GITHUB_OUTPUT
fi
+ test_encrypted_config_tpm:
+ needs: build_iso
+ runs-on: ubuntu-24.04
+ timeout-minutes: 30
+ container:
+ image: vyos/vyos-build:current
+ options: --sysctl net.ipv6.conf.lo.disable_ipv6=0 --privileged
+ outputs:
+ exit_code: ${{ steps.test.outputs.exit_code }}
+ steps:
+ # We need the test script from vyos-build repo
+ - name: Clone vyos-build source code
+ uses: actions/checkout@v4
+ with:
+ repository: vyos/vyos-build
+ - uses: actions/download-artifact@v4
+ with:
+ name: vyos-${{ needs.build_iso.outputs.build_version }}
+ path: build
+ - name: VyOS TPM encryption tests
+ id: test
+ shell: bash
+ run: |
+ set -e
+ sudo make testtpm
+ if [[ $? == 0 ]]; then
+ echo "exit_code=success" >> $GITHUB_OUTPUT
+ else
+ echo "exit_code=fail" >> $GITHUB_OUTPUT
+ fi
+
result:
needs:
- test_smoketest_cli
- test_config_load
- test_raid1_install
+ - test_encrypted_config_tpm
runs-on: ubuntu-24.04
timeout-minutes: 5
if: always()
@@ -177,6 +212,7 @@ jobs:
* CLI Smoketests ${{ needs.test_smoketest_cli.outputs.exit_code == 'success' && '👍 passed' || '❌ failed' }}
* Config tests ${{ needs.test_config_load.outputs.exit_code == 'success' && '👍 passed' || '❌ failed' }}
* RAID1 tests ${{ needs.test_raid1_install.outputs.exit_code == 'success' && '👍 passed' || '❌ failed' }}
+ * TPM tests ${{ needs.test_encrypted_config_tpm.outputs.exit_code == 'success' && '👍 passed' || '❌ failed' }}
message-id: "SMOKETEST_RESULTS"
allow-repeats: false
diff --git a/.github/workflows/trigger-rebuild-repo-package.yml b/.github/workflows/trigger-rebuild-repo-package.yml
new file mode 100644
index 000000000..37ec83274
--- /dev/null
+++ b/.github/workflows/trigger-rebuild-repo-package.yml
@@ -0,0 +1,32 @@
+name: Trigger to build a deb package from repo
+
+on:
+ pull_request_target:
+ 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 }}
+ 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/chrony/chrony.conf.j2 b/data/templates/chrony/chrony.conf.j2
index e3f078fdc..2838f5524 100644
--- a/data/templates/chrony/chrony.conf.j2
+++ b/data/templates/chrony/chrony.conf.j2
@@ -42,7 +42,7 @@ user {{ user }}
{% if config.pool is vyos_defined %}
{% set association = 'pool' %}
{% endif %}
-{{ association }} {{ server | replace('_', '-') }} iburst {{ 'nts' if config.nts is vyos_defined }} {{ 'noselect' if config.noselect is vyos_defined }} {{ 'prefer' if config.prefer is vyos_defined }}
+{{ association }} {{ server | replace('_', '-') }} iburst {{- ' nts' if config.nts is vyos_defined }} {{- ' noselect' if config.noselect is vyos_defined }} {{- ' prefer' if config.prefer is vyos_defined }} {{- ' xleave' if config.interleave is vyos_defined }} {{- ' port ' ~ ptp.port if ptp.port is vyos_defined and config.ptp is vyos_defined }}
{% endfor %}
{% endif %}
@@ -66,3 +66,18 @@ bindaddress {{ address }}
binddevice {{ interface }}
{% endif %}
{% endif %}
+
+{% if ptp.timestamp.interface is vyos_defined %}
+# Enable hardware timestamping on the specified interfaces
+{% for iface, iface_config in ptp.timestamp.interface.items() %}
+{% if iface == "all" %}
+{% set iface = "*" %}
+{% endif %}
+hwtimestamp {{ iface }} {{- ' rxfilter ' ~ iface_config.receive_filter if iface_config.receive_filter is vyos_defined }}
+{% endfor %}
+{% endif %}
+
+{% if ptp.port is vyos_defined %}
+# Enable sending and receiving NTP over PTP packets (PTP transport)
+ptpport {{ ptp.port }}
+{% endif %}
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/frr/daemons.frr.tmpl b/data/templates/frr/daemons.frr.tmpl
index 339b4e52f..3506528d2 100644
--- a/data/templates/frr/daemons.frr.tmpl
+++ b/data/templates/frr/daemons.frr.tmpl
@@ -36,7 +36,7 @@ babeld=yes
sharpd=no
pbrd=no
bfdd=yes
-fabricd=no
+fabricd=yes
vrrpd=no
pathd=no
diff --git a/data/templates/frr/fabricd.frr.j2 b/data/templates/frr/fabricd.frr.j2
new file mode 100644
index 000000000..8f2ae6466
--- /dev/null
+++ b/data/templates/frr/fabricd.frr.j2
@@ -0,0 +1,72 @@
+!
+{% for name, router_config in domain.items() %}
+{% if router_config.interface is vyos_defined %}
+{% for iface, iface_config in router_config.interface.items() %}
+interface {{ iface }}
+{% if iface_config.address_family.ipv4 is vyos_defined %}
+ ip router openfabric {{ name }}
+{% endif %}
+{% if iface_config.address_family.ipv6 is vyos_defined %}
+ ipv6 router openfabric {{ name }}
+{% endif %}
+{% if iface_config.csnp_interval is vyos_defined %}
+ openfabric csnp-interval {{ iface_config.csnp_interval }}
+{% endif %}
+{% if iface_config.hello_interval is vyos_defined %}
+ openfabric hello-interval {{ iface_config.hello_interval }}
+{% endif %}
+{% if iface_config.hello_multiplier is vyos_defined %}
+ openfabric hello-multiplier {{ iface_config.hello_multiplier }}
+{% endif %}
+{% if iface_config.metric is vyos_defined %}
+ openfabric metric {{ iface_config.metric }}
+{% endif %}
+{% if iface_config.passive is vyos_defined or iface == 'lo' %}
+ openfabric passive
+{% endif %}
+{% if iface_config.password.md5 is vyos_defined %}
+ openfabric password md5 {{ iface_config.password.md5 }}
+{% elif iface_config.password.plaintext_password is vyos_defined %}
+ openfabric password clear {{ iface_config.password.plaintext_password }}
+{% endif %}
+{% if iface_config.psnp_interval is vyos_defined %}
+ openfabric psnp-interval {{ iface_config.psnp_interval }}
+{% endif %}
+exit
+!
+{% endfor %}
+{% endif %}
+router openfabric {{ name }}
+ net {{ net }}
+{% if router_config.domain_password.md5 is vyos_defined %}
+ domain-password md5 {{ router_config.domain_password.plaintext_password }}
+{% elif router_config.domain_password.plaintext_password is vyos_defined %}
+ domain-password clear {{ router_config.domain_password.plaintext_password }}
+{% endif %}
+{% if router_config.log_adjacency_changes is vyos_defined %}
+ log-adjacency-changes
+{% endif %}
+{% if router_config.set_overload_bit is vyos_defined %}
+ set-overload-bit
+{% endif %}
+{% if router_config.purge_originator is vyos_defined %}
+ purge-originator
+{% endif %}
+{% if router_config.fabric_tier is vyos_defined %}
+ fabric-tier {{ router_config.fabric_tier }}
+{% endif %}
+{% if router_config.lsp_gen_interval is vyos_defined %}
+ lsp-gen-interval {{ router_config.lsp_gen_interval }}
+{% endif %}
+{% if router_config.lsp_refresh_interval is vyos_defined %}
+ lsp-refresh-interval {{ router_config.lsp_refresh_interval }}
+{% endif %}
+{% if router_config.max_lsp_lifetime is vyos_defined %}
+ max-lsp-lifetime {{ router_config.max_lsp_lifetime }}
+{% endif %}
+{% if router_config.spf_interval is vyos_defined %}
+ spf-interval {{ router_config.spf_interval }}
+{% endif %}
+exit
+!
+{% endfor %}
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/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
index 97e0ee0b7..7fd592d1f 100644
--- a/data/templates/rsyslog/rsyslog.conf.j2
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -10,6 +10,10 @@ $MarkMessagePeriod {{ global.marker.interval }}
$PreserveFQDN on
{% endif %}
+{% if global.local_host_name is vyos_defined %}
+$LocalHostName {{ global.local_host_name }}
+{% endif %}
+
# We always log to /var/log/messages
$outchannel global,/var/log/messages,262144,/usr/sbin/logrotate {{ logrotate }}
{% if global.facility is vyos_defined %}
@@ -54,12 +58,10 @@ $outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archiv
{% endif %}
{% if host_options.protocol is vyos_defined('tcp') %}
{% if host_options.format.octet_counted is vyos_defined %}
-{{ tmp | join(';') }} @@(o){{ host_name | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format
-{% else %}
-{{ tmp | join(';') }} @@{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}
+{{ tmp | join(';') }} @@{{ '(o)' if host_options.format.octet_counted is vyos_defined }}{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.include_timezone is vyos_defined }}
{% endif %}
{% else %}
-{{ tmp | join(';') }} @{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.octet_counted is vyos_defined }}
+{{ tmp | join(';') }} @{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.include_timezone is vyos_defined }}
{% endif %}
{% endfor %}
{% endif %}
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..f8cfb876c 100644
--- a/debian/control
+++ b/debian/control
@@ -94,7 +94,7 @@ Depends:
linux-cpupower,
# ipaddrcheck is widely used in IP value validators
ipaddrcheck,
- ethtool,
+ ethtool (>= 6.10),
lm-sensors,
procps,
netplug,
@@ -113,8 +113,11 @@ Depends:
efibootmgr,
libefivar1,
dosfstools,
- grub-efi-amd64-bin [amd64],
+ grub-efi-amd64-signed [amd64],
grub-efi-arm64-bin [arm64],
+ mokutil [amd64],
+ shim-signed [amd64],
+ sbsigntool [amd64],
# Image signature verification tool
minisign,
# Live filesystem tools
@@ -149,6 +152,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/include/isis/protocol-common-config.xml.i b/interface-definitions/include/isis/protocol-common-config.xml.i
index 0e79ca5f2..35ce80be9 100644
--- a/interface-definitions/include/isis/protocol-common-config.xml.i
+++ b/interface-definitions/include/isis/protocol-common-config.xml.i
@@ -86,12 +86,7 @@
</constraint>
</properties>
</leafNode>
-<leafNode name="log-adjacency-changes">
- <properties>
- <help>Log adjacency state changes</help>
- <valueless/>
- </properties>
-</leafNode>
+#include <include/log-adjacency-changes.xml.i>
<leafNode name="lsp-gen-interval">
<properties>
<help>Minimum interval between regenerating same LSP</help>
@@ -208,18 +203,7 @@
#include <include/isis/lfa-protocol.xml.i>
</children>
</node>
-<leafNode name="net">
- <properties>
- <help>A Network Entity Title for this process (ISO only)</help>
- <valueHelp>
- <format>XX.XXXX. ... .XXX.XX</format>
- <description>Network entity title (NET)</description>
- </valueHelp>
- <constraint>
- <regex>[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}</regex>
- </constraint>
- </properties>
-</leafNode>
+#include <include/net.xml.i>
<leafNode name="purge-originator">
<properties>
<help>Use the RFC 6232 purge-originator</help>
diff --git a/interface-definitions/include/log-adjacency-changes.xml.i b/interface-definitions/include/log-adjacency-changes.xml.i
new file mode 100644
index 000000000..a0628b8e2
--- /dev/null
+++ b/interface-definitions/include/log-adjacency-changes.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from log-adjacency-changes.xml.i -->
+<leafNode name="log-adjacency-changes">
+ <properties>
+ <help>Log changes in adjacency state</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/net.xml.i b/interface-definitions/include/net.xml.i
new file mode 100644
index 000000000..10b54ee49
--- /dev/null
+++ b/interface-definitions/include/net.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from net.xml.i -->
+<leafNode name="net">
+ <properties>
+ <help>A Network Entity Title for the process (ISO only)</help>
+ <valueHelp>
+ <format>XX.XXXX. ... .XXX.XX</format>
+ <description>Network entity title (NET)</description>
+ </valueHelp>
+ <constraint>
+ <regex>[a-fA-F0-9]{2}(\.[a-fA-F0-9]{4}){3,9}\.[a-fA-F0-9]{2}</regex>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/openfabric/password.xml.i b/interface-definitions/include/openfabric/password.xml.i
new file mode 100644
index 000000000..fa34a4dab
--- /dev/null
+++ b/interface-definitions/include/openfabric/password.xml.i
@@ -0,0 +1,20 @@
+<!-- include start from openfabric/password.xml.i -->
+<leafNode name="plaintext-password">
+ <properties>
+ <help>Use plain text password</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Authentication password</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<leafNode name="md5">
+ <properties>
+ <help>Use MD5 hash authentication</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Authentication password</description>
+ </valueHelp>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in
index cc0327f3d..b17cad478 100644
--- a/interface-definitions/interfaces_bonding.xml.in
+++ b/interface-definitions/interfaces_bonding.xml.in
@@ -56,6 +56,7 @@
#include <include/interface/disable.xml.i>
#include <include/interface/vrf.xml.i>
#include <include/interface/mirror.xml.i>
+ #include <include/interface/eapol.xml.i>
<node name="evpn">
<properties>
<help>EVPN Multihoming</help>
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/protocols_openfabric.xml.in b/interface-definitions/protocols_openfabric.xml.in
new file mode 100644
index 000000000..81200360e
--- /dev/null
+++ b/interface-definitions/protocols_openfabric.xml.in
@@ -0,0 +1,218 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="protocols">
+ <children>
+ <node name="openfabric" owner="${vyos_conf_scripts_dir}/protocols_openfabric.py">
+ <properties>
+ <help>OpenFabric protocol</help>
+ <priority>680</priority>
+ </properties>
+ <children>
+ #include <include/net.xml.i>
+ <tagNode name="domain">
+ <properties>
+ <help>OpenFabric process name</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>Domain name</description>
+ </valueHelp>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface params</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ </constraint>
+ </properties>
+ <children>
+ <node name="address-family">
+ <properties>
+ <help>Openfabric address family</help>
+ </properties>
+ <children>
+ <leafNode name="ipv4">
+ <properties>
+ <help>IPv4 OpenFabric</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="ipv6">
+ <properties>
+ <help>IPv6 OpenFabric</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="csnp-interval">
+ <properties>
+ <help>Complete Sequence Number Packets (CSNP) interval</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>CSNP interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="hello-interval">
+ <properties>
+ <help>Hello interval</help>
+ <valueHelp>
+ <format>u32:1-600</format>
+ <description>Hello interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-600"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="hello-multiplier">
+ <properties>
+ <help>Multiplier for Hello holding time</help>
+ <valueHelp>
+ <format>u32:2-100</format>
+ <description>Multiplier for Hello holding time</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 2-100"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="metric">
+ <properties>
+ <help>Interface metric value</help>
+ <valueHelp>
+ <format>u32:0-16777215</format>
+ <description>Interface metric value</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-16777215"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="passive">
+ <properties>
+ <help>Do not initiate adjacencies to the interface</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="password">
+ <properties>
+ <help>Authentication password for the interface</help>
+ </properties>
+ <children>
+ #include <include/openfabric/password.xml.i>
+ </children>
+ </node>
+ <leafNode name="psnp-interval">
+ <properties>
+ <help>Partial Sequence Number Packets (PSNP) interval</help>
+ <valueHelp>
+ <format>u32:0-120</format>
+ <description>PSNP interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-120"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <node name="domain-password">
+ <properties>
+ <help>Authentication password for a routing domain</help>
+ </properties>
+ <children>
+ #include <include/openfabric/password.xml.i>
+ </children>
+ </node>
+ #include <include/log-adjacency-changes.xml.i>
+ <leafNode name="set-overload-bit">
+ <properties>
+ <help>Overload bit to avoid any transit traffic</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="purge-originator">
+ <properties>
+ <help>RFC 6232 purge originator identification</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="fabric-tier">
+ <properties>
+ <help>Static tier number to advertise as location in the fabric</help>
+ <valueHelp>
+ <format>u32:0-14</format>
+ <description>Static tier number</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-14"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lsp-gen-interval">
+ <properties>
+ <help>Minimum interval between regenerating same link-state packet (LSP)</help>
+ <valueHelp>
+ <format>u32:1-120</format>
+ <description>Minimum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-120"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="lsp-refresh-interval">
+ <properties>
+ <help>Link-state packet (LSP) refresh interval</help>
+ <valueHelp>
+ <format>u32:1-65235</format>
+ <description>LSP refresh interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-65235"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="max-lsp-lifetime">
+ <properties>
+ <help>Maximum link-state packet lifetime</help>
+ <valueHelp>
+ <format>u32:360-65535</format>
+ <description>Maximum LSP lifetime in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 360-65535"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="spf-interval">
+ <properties>
+ <help>Minimum interval between SPF calculations</help>
+ <valueHelp>
+ <format>u32:1-120</format>
+ <description>Interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-120"/>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
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_lldp.xml.in b/interface-definitions/service_lldp.xml.in
index 1a06e0cb3..51a9f9cce 100644
--- a/interface-definitions/service_lldp.xml.in
+++ b/interface-definitions/service_lldp.xml.in
@@ -23,6 +23,10 @@
<script>${vyos_completion_dir}/list_interfaces</script>
<list>all</list>
</completionHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ <regex>all</regex>
+ </constraint>
</properties>
<children>
#include <include/generic-disable-node.xml.i>
diff --git a/interface-definitions/service_ntp.xml.in b/interface-definitions/service_ntp.xml.in
index c057b62b5..5dc0cd295 100644
--- a/interface-definitions/service_ntp.xml.in
+++ b/interface-definitions/service_ntp.xml.in
@@ -13,6 +13,74 @@
#include <include/generic-interface.xml.i>
#include <include/listen-address.xml.i>
#include <include/interface/vrf.xml.i>
+ <node name="ptp">
+ <properties>
+ <help>Enable Precision Time Protocol (PTP) transport</help>
+ </properties>
+ <children>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>319</defaultValue>
+ </leafNode>
+ <node name="timestamp">
+ <properties>
+ <help>Enable timestamping of packets in the NIC hardware</help>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface to enable timestamping on</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ <list>all</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>Select all interfaces</description>
+ </valueHelp>
+ <valueHelp>
+ <format>txt</format>
+ <description>Interface name</description>
+ </valueHelp>
+ <constraint>
+ #include <include/constraint/interface-name.xml.i>
+ <regex>all</regex>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="receive-filter">
+ <properties>
+ <help>Selects which inbound packets are timestamped by the NIC</help>
+ <completionHelp>
+ <list>all ntp ptp none</list>
+ </completionHelp>
+ <valueHelp>
+ <format>all</format>
+ <description>All packets are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ntp</format>
+ <description>Only NTP packets are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ptp</format>
+ <description>Only PTP or NTP packets using the PTP transport are timestamped</description>
+ </valueHelp>
+ <valueHelp>
+ <format>none</format>
+ <description>No packet is timestamped</description>
+ </valueHelp>
+ <constraint>
+ <regex>(all|ntp|ptp|none)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
<leafNode name="leap-second">
<properties>
<help>Leap second behavior</help>
@@ -86,6 +154,18 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="ptp">
+ <properties>
+ <help>Use Precision Time Protocol (PTP) transport for the server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="interleave">
+ <properties>
+ <help>Use the interleaved mode for the server</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
</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/interface-definitions/system_option.xml.in b/interface-definitions/system_option.xml.in
index e78a53552..dc9958ff5 100644
--- a/interface-definitions/system_option.xml.in
+++ b/interface-definitions/system_option.xml.in
@@ -49,6 +49,26 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="amd-pstate-driver">
+ <properties>
+ <help>Enables and configures pstate driver for AMD Ryzen and Epyc CPUs</help>
+ <completionHelp>
+ <list>active passive guided</list>
+ </completionHelp>
+ <valueHelp>
+ <format>active</format>
+ <description>The firmware controls performance states and the system governor has no effect</description>
+ </valueHelp>
+ <valueHelp>
+ <format>passive</format>
+ <description>Allow the system governor to manage performance states</description>
+ </valueHelp>
+ <valueHelp>
+ <format>guided</format>
+ <description>The firmware controls performance states guided by the system governor</description>
+ </valueHelp>
+ </properties>
+ </leafNode>
<node name="debug">
<properties>
<help>Dynamic debugging for kernel module</help>
diff --git a/interface-definitions/system_syslog.xml.in b/interface-definitions/system_syslog.xml.in
index 3343e2c59..0a9a00572 100644
--- a/interface-definitions/system_syslog.xml.in
+++ b/interface-definitions/system_syslog.xml.in
@@ -66,6 +66,12 @@
<valueless/>
</properties>
</leafNode>
+ <leafNode name="include-timezone">
+ <properties>
+ <help>Include system timezone in syslog message</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</node>
</children>
diff --git a/op-mode-definitions/date.xml.in b/op-mode-definitions/date.xml.in
index 6d8586025..4e62a8335 100644
--- a/op-mode-definitions/date.xml.in
+++ b/op-mode-definitions/date.xml.in
@@ -35,7 +35,7 @@
<list>&lt;MMDDhhmm&gt; &lt;MMDDhhmmYY&gt; &lt;MMDDhhmmCCYY&gt; &lt;MMDDhhmmCCYY.ss&gt;</list>
</completionHelp>
</properties>
- <command>/bin/date "$3"</command>
+ <command>sudo bash -c "/bin/date '$3' &amp;&amp; hwclock --systohc --localtime"</command>
</tagNode>
</children>
</node>
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/execute-ssh.xml.in b/op-mode-definitions/execute-ssh.xml.in
new file mode 100644
index 000000000..7fa656f5e
--- /dev/null
+++ b/op-mode-definitions/execute-ssh.xml.in
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <children>
+ <node name="ssh">
+ <properties>
+ <help>SSH to a node</help>
+ </properties>
+ <children>
+ <tagNode name="host">
+ <properties>
+ <help>Hostname or IP address</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>/usr/bin/ssh $4</command>
+ <children>
+ <tagNode name="user">
+ <properties>
+ <help>Remote server username</help>
+ <completionHelp>
+ <list>&lt;username&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>/usr/bin/ssh $6@$4</command>
+ </tagNode>
+ </children>
+ </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/firewall.xml.in b/op-mode-definitions/firewall.xml.in
index b6ce5bae2..82e6c8668 100644..100755
--- a/op-mode-definitions/firewall.xml.in
+++ b/op-mode-definitions/firewall.xml.in
@@ -98,6 +98,138 @@
</node>
</children>
</node>
+ <node name="input">
+ <properties>
+ <help>Show bridge input firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show bridge input filter firewall ruleset</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of bridge input filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge input filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of bridge input filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge input filter rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of specific bridge input filter firewall rule</help>
+ <completionHelp>
+ <path>firewall bridge input filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
+ <node name="output">
+ <properties>
+ <help>Show bridge output firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show bridge output filter firewall ruleset</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of bridge output filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge output filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of bridge output filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge output filter rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of specific bridge output filter firewall rule</help>
+ <completionHelp>
+ <path>firewall bridge output filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
+ <node name="prerouting">
+ <properties>
+ <help>Show bridge prerouting firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show bridge prerouting filter firewall ruleset</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of bridge prerouting filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge prerouting filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of bridge prerouting filter firewall rules</help>
+ <completionHelp>
+ <path>firewall bridge prerouting filter rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of specific bridge prerouting filter firewall rule</help>
+ <completionHelp>
+ <path>firewall bridge prerouting filter detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
<tagNode name="name">
<properties>
<help>Show bridge custom firewall chains</help>
@@ -278,6 +410,50 @@
</node>
</children>
</node>
+ <node name="prerouting">
+ <properties>
+ <help>Show IPv6 prerouting firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="raw">
+ <properties>
+ <help>Show IPv6 prerouting raw firewall ruleset</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of IPv6 prerouting raw firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv6 prerouting raw detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv6 prerouting raw firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6 prerouting raw rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of IPv6 prerouting raw firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv6 prerouting raw rule detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
<tagNode name="name">
<properties>
<help>Show IPv6 custom firewall chains</help>
@@ -458,6 +634,50 @@
</node>
</children>
</node>
+ <node name="prerouting">
+ <properties>
+ <help>Show IPv4 prerouting firewall ruleset</help>
+ </properties>
+ <children>
+ <node name="raw">
+ <properties>
+ <help>Show IPv4 prerouting raw firewall ruleset</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of IPv4 prerouting raw firewall ruleset</help>
+ <completionHelp>
+ <path>firewall ipv4 prerouting raw detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --detail $6</command>
+ </leafNode>
+ <tagNode name="rule">
+ <properties>
+ <help>Show summary of IPv4 prerouting raw firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv4 prerouting raw rule</path>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show list view of IPv4 prerouting raw firewall rules</help>
+ <completionHelp>
+ <path>firewall ipv4 prerouting raw rule detail</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7 --detail $8</command>
+ </leafNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5 --rule $7</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/firewall.py --action show --family $3 --hook $4 --priority $5</command>
+ </node>
+ </children>
+ </node>
<tagNode name="name">
<properties>
<help>Show IPv4 custom firewall chains</help>
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/include/show-route-openfabric.xml.i b/op-mode-definitions/include/show-route-openfabric.xml.i
new file mode 100644
index 000000000..ae1ef380e
--- /dev/null
+++ b/op-mode-definitions/include/show-route-openfabric.xml.i
@@ -0,0 +1,8 @@
+<!-- included start from show-route-openfabric.xml.i -->
+<leafNode name="openfabric">
+ <properties>
+ <help>OpenFabric routes</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+</leafNode>
+<!-- included end -->
diff --git a/op-mode-definitions/install-mok.xml.in b/op-mode-definitions/install-mok.xml.in
new file mode 100644
index 000000000..18526a354
--- /dev/null
+++ b/op-mode-definitions/install-mok.xml.in
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interfaceDefinition>
+ <node name="install">
+ <children>
+ <leafNode name="mok">
+ <properties>
+ <help>Install Secure Boot MOK (Machine Owner Key)</help>
+ </properties>
+ <command>if test -f /var/lib/shim-signed/mok/MOK.der; then sudo mokutil --ignore-keyring --import /var/lib/shim-signed/mok/MOK.der; else echo "Secure Boot Machine Owner Key not found"; fi</command>
+ </leafNode>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in
index a2d5d924a..6a2b7e53b 100644
--- a/op-mode-definitions/monitor-log.xml.in
+++ b/op-mode-definitions/monitor-log.xml.in
@@ -237,6 +237,12 @@
</properties>
<command>journalctl --follow --no-hostname --boot /usr/lib/frr/isisd</command>
</leafNode>
+ <leafNode name="openfabric">
+ <properties>
+ <help>Monitor log for OpenFabric</help>
+ </properties>
+ <command>journalctl --follow --no-hostname --boot /usr/lib/frr/fabricd</command>
+ </leafNode>
<leafNode name="nhrp">
<properties>
<help>Monitor log for NHRP</help>
diff --git a/op-mode-definitions/restart-frr.xml.in b/op-mode-definitions/restart-frr.xml.in
index 2c9d4b1cc..4772e8dd2 100644
--- a/op-mode-definitions/restart-frr.xml.in
+++ b/op-mode-definitions/restart-frr.xml.in
@@ -56,6 +56,12 @@
</properties>
<command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon isisd</command>
</leafNode>
+ <leafNode name="openfabric">
+ <properties>
+ <help>Restart OpenFabric routing daemon</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/restart_frr.py --action restart --daemon fabricd</command>
+ </leafNode>
<leafNode name="pim6">
<properties>
<help>Restart IPv6 Protocol Independent Multicast (PIM) daemon</help>
diff --git a/op-mode-definitions/show-interfaces-wireguard.xml.in b/op-mode-definitions/show-interfaces-wireguard.xml.in
index bab7f19c8..0e61ccd74 100644
--- a/op-mode-definitions/show-interfaces-wireguard.xml.in
+++ b/op-mode-definitions/show-interfaces-wireguard.xml.in
@@ -41,7 +41,7 @@
<properties>
<help>Shows current configuration and device information</help>
</properties>
- <command>sudo wg show "$4"</command>
+ <command>sudo ${vyos_op_scripts_dir}/interfaces_wireguard.py show_summary --intf-name="$4"</command>
</leafNode>
</children>
</tagNode>
diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in
index c878bf712..37279d3d2 100644
--- a/op-mode-definitions/show-ip-route.xml.in
+++ b/op-mode-definitions/show-ip-route.xml.in
@@ -46,6 +46,7 @@
<command>ip -s route list $5</command>
</tagNode>
#include <include/show-route-isis.xml.i>
+ #include <include/show-route-openfabric.xml.i>
#include <include/show-route-kernel.xml.i>
#include <include/show-route-ospf.xml.i>
#include <include/show-route-rip.xml.i>
diff --git a/op-mode-definitions/show-ipv6-route.xml.in b/op-mode-definitions/show-ipv6-route.xml.in
index d73fb46b4..f68a94971 100644
--- a/op-mode-definitions/show-ipv6-route.xml.in
+++ b/op-mode-definitions/show-ipv6-route.xml.in
@@ -46,6 +46,7 @@
<command>ip -s -f inet6 route list $5</command>
</tagNode>
#include <include/show-route-isis.xml.i>
+ #include <include/show-route-openfabric.xml.i>
#include <include/show-route-kernel.xml.i>
#include <include/show-route-ospfv3.xml.i>
#include <include/show-route-ripng.xml.i>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index 7ae3b890b..c2504686d 100644..100755
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -172,6 +172,81 @@
</node>
</children>
</node>
+ <node name="input">
+ <properties>
+ <help>Show Bridge input firewall log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-INP</command>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show Bridge firewall input filter</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-INP-filter</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show log for a rule in the specified firewall</help>
+ <completionHelp>
+ <path>firewall bridge input filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[bri-INP-filter-$8-[ADRJC]\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="output">
+ <properties>
+ <help>Show Bridge output firewall log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-OUT</command>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show Bridge firewall output filter</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-OUT-filter</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show log for a rule in the specified firewall</help>
+ <completionHelp>
+ <path>firewall bridge output filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[bri-OUT-filter-$8-[ADRJC]\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+ <node name="prerouting">
+ <properties>
+ <help>Show Bridge prerouting firewall log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-PRE</command>
+ <children>
+ <node name="filter">
+ <properties>
+ <help>Show Bridge firewall prerouting filter</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep bri-PRE-filter</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show log for a rule in the specified firewall</help>
+ <completionHelp>
+ <path>firewall bridge prerouting filter rule</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[bri-PRE-filter-$8-[ADRJC]\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
<tagNode name="name">
<properties>
<help>Show custom Bridge firewall log</help>
@@ -295,6 +370,31 @@
</node>
</children>
</node>
+ <node name="prerouting">
+ <properties>
+ <help>Show firewall IPv4 prerouting log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep ipv4-PRE</command>
+ <children>
+ <node name="raw">
+ <properties>
+ <help>Show firewall IPv4 prerouting raw log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep ipv4-PRE-raw</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show log for a rule in the specified firewall</help>
+ <completionHelp>
+ <path>firewall ipv4 prerouting raw rule</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[ipv4-PRE-raw-$8-[ADRJC]\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
<node name="ipv6">
@@ -398,6 +498,31 @@
</node>
</children>
</node>
+ <node name="prerouting">
+ <properties>
+ <help>Show firewall IPv6 prerouting log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep ipv6-PRE</command>
+ <children>
+ <node name="raw">
+ <properties>
+ <help>Show firewall IPv6 prerouting raw log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | grep ipv6-PRE-raw</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show log for a rule in the specified firewall</help>
+ <completionHelp>
+ <path>firewall ipv6 prerouting raw rule</path>
+ </completionHelp>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[ipv6-PRE-raw-$8-[ADRJC]\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
</children>
</node>
</children>
@@ -642,6 +767,12 @@
</properties>
<command>journalctl --boot /usr/lib/frr/isisd</command>
</leafNode>
+ <leafNode name="openfabric">
+ <properties>
+ <help>Show log for OpenFabric</help>
+ </properties>
+ <command>journalctl --boot /usr/lib/frr/fabricd</command>
+ </leafNode>
<leafNode name="nhrp">
<properties>
<help>Show log for NHRP</help>
diff --git a/op-mode-definitions/show-openfabric.xml.in b/op-mode-definitions/show-openfabric.xml.in
new file mode 100644
index 000000000..2f489866e
--- /dev/null
+++ b/op-mode-definitions/show-openfabric.xml.in
@@ -0,0 +1,51 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="openfabric">
+ <properties>
+ <help>Show OpenFabric routing protocol</help>
+ </properties>
+ <children>
+ <node name="database">
+ <properties>
+ <help>Show OpenFabric link state database</help>
+ </properties>
+ <children>
+ #include <include/vtysh-generic-detail.xml.i>
+ </children>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ <node name="interface">
+ <properties>
+ <help>Show OpenFabric interfaces</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces</script>
+ </completionHelp>
+ </properties>
+ <children>
+ #include <include/vtysh-generic-detail.xml.i>
+ </children>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ #include <include/vtysh-generic-interface-tagNode.xml.i>
+ <node name="neighbor">
+ <properties>
+ <help>Show OpenFabric neighbor adjacencies</help>
+ </properties>
+ <children>
+ #include <include/vtysh-generic-detail.xml.i>
+ </children>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </node>
+ <leafNode name="summary">
+ <properties>
+ <help>Show OpenFabric information summary</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-secure-boot.xml.in b/op-mode-definitions/show-secure-boot.xml.in
new file mode 100644
index 000000000..ff731bac9
--- /dev/null
+++ b/op-mode-definitions/show-secure-boot.xml.in
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="show">
+ <children>
+ <node name="secure-boot">
+ <properties>
+ <help>Show Secure Boot state</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/secure_boot.py show</command>
+ <children>
+ <leafNode name="keys">
+ <properties>
+ <help>Show enrolled certificates</help>
+ </properties>
+ <command>mokutil --list-enrolled</command>
+ </leafNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
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/configverify.py b/python/vyos/configverify.py
index 59b67300d..92996f2ee 100644
--- a/python/vyos/configverify.py
+++ b/python/vyos/configverify.py
@@ -520,3 +520,20 @@ def verify_pki_dh_parameters(config: dict, dh_name: str, min_key_size: int=0):
dh_bits = dh_numbers.p.bit_length()
if dh_bits < min_key_size:
raise ConfigError(f'Minimum DH key-size is {min_key_size} bits!')
+
+def verify_eapol(config: dict):
+ """
+ Common helper function used by interface implementations to perform
+ recurring validation of EAPoL configuration.
+ """
+ if 'eapol' not in config:
+ return
+
+ if 'certificate' not in config['eapol']:
+ raise ConfigError('Certificate must be specified when using EAPoL!')
+
+ verify_pki_certificate(config, config['eapol']['certificate'], no_password_protected=True)
+
+ if 'ca_certificate' in config['eapol']:
+ for ca_cert in config['eapol']['ca_certificate']:
+ verify_pki_ca_certificate(config, ca_cert)
diff --git a/python/vyos/ethtool.py b/python/vyos/ethtool.py
index 80bb56fa2..21272cc5b 100644
--- a/python/vyos/ethtool.py
+++ b/python/vyos/ethtool.py
@@ -56,11 +56,8 @@ class Ethtool:
# '100' : {'full': '', 'half': ''},
# '1000': {'full': ''}
# }
- _speed_duplex = {'auto': {'auto': ''}}
_ring_buffer = None
_driver_name = None
- _auto_negotiation = False
- _auto_negotiation_supported = None
_flow_control = None
def __init__(self, ifname):
@@ -74,56 +71,51 @@ class Ethtool:
self._driver_name = driver.group(1)
# Build a dictinary of supported link-speed and dupley settings.
- out, _ = popen(f'ethtool {ifname}')
- reading = False
- pattern = re.compile(r'\d+base.*')
- for line in out.splitlines()[1:]:
- line = line.lstrip()
- if 'Supported link modes:' in line:
- reading = True
- if 'Supported pause frame use:' in line:
- reading = False
- if reading:
- for block in line.split():
- if pattern.search(block):
- speed = block.split('base')[0]
- duplex = block.split('/')[-1].lower()
- if speed not in self._speed_duplex:
- self._speed_duplex.update({ speed : {}})
- if duplex not in self._speed_duplex[speed]:
- self._speed_duplex[speed].update({ duplex : ''})
- if 'Supports auto-negotiation:' in line:
- # Split the following string: Auto-negotiation: off
- # we are only interested in off or on
- tmp = line.split()[-1]
- self._auto_negotiation_supported = bool(tmp == 'Yes')
- # Only read in if Auto-negotiation is supported
- if self._auto_negotiation_supported and 'Auto-negotiation:' in line:
- # Split the following string: Auto-negotiation: off
- # we are only interested in off or on
- tmp = line.split()[-1]
- self._auto_negotiation = bool(tmp == 'on')
+ # [ {
+ # "ifname": "eth0",
+ # "supported-ports": [ "TP" ],
+ # "supported-link-modes": [ "10baseT/Half","10baseT/Full","100baseT/Half","100baseT/Full","1000baseT/Full" ],
+ # "supported-pause-frame-use": "Symmetric",
+ # "supports-auto-negotiation": true,
+ # "supported-fec-modes": [ ],
+ # "advertised-link-modes": [ "10baseT/Half","10baseT/Full","100baseT/Half","100baseT/Full","1000baseT/Full" ],
+ # "advertised-pause-frame-use": "Symmetric",
+ # "advertised-auto-negotiation": true,
+ # "advertised-fec-modes": [ ],
+ # "speed": 1000,
+ # "duplex": "Full",
+ # "auto-negotiation": false,
+ # "port": "Twisted Pair",
+ # "phyad": 1,
+ # "transceiver": "internal",
+ # "supports-wake-on": "pumbg",
+ # "wake-on": "g",
+ # "current-message-level": 7,
+ # "link-detected": true
+ # } ]
+ out, _ = popen(f'ethtool --json {ifname}')
+ self._base_settings = loads(out)[0]
# Now populate driver features
out, _ = popen(f'ethtool --json --show-features {ifname}')
- self._features = loads(out)
+ self._features = loads(out)[0]
# Get information about NIC ring buffers
out, _ = popen(f'ethtool --json --show-ring {ifname}')
- self._ring_buffer = loads(out)
+ self._ring_buffer = loads(out)[0]
# Get current flow control settings, but this is not supported by
# all NICs (e.g. vmxnet3 does not support is)
out, err = popen(f'ethtool --json --show-pause {ifname}')
if not bool(err):
- self._flow_control = loads(out)
+ self._flow_control = loads(out)[0]
def check_auto_negotiation_supported(self):
""" Check if the NIC supports changing auto-negotiation """
- return self._auto_negotiation_supported
+ return self._base_settings['supports-auto-negotiation']
def get_auto_negotiation(self):
- return self._auto_negotiation_supported and self._auto_negotiation
+ return self._base_settings['supports-auto-negotiation'] and self._base_settings['auto-negotiation']
def get_driver_name(self):
return self._driver_name
@@ -137,9 +129,9 @@ class Ethtool:
"""
active = False
fixed = True
- if feature in self._features[0]:
- active = bool(self._features[0][feature]['active'])
- fixed = bool(self._features[0][feature]['fixed'])
+ if feature in self._features:
+ active = bool(self._features[feature]['active'])
+ fixed = bool(self._features[feature]['fixed'])
return active, fixed
def get_generic_receive_offload(self):
@@ -165,14 +157,14 @@ class Ethtool:
# thus when it's impossible return None
if rx_tx not in ['rx', 'tx']:
ValueError('Ring-buffer type must be either "rx" or "tx"')
- return str(self._ring_buffer[0].get(f'{rx_tx}-max', None))
+ return str(self._ring_buffer.get(f'{rx_tx}-max', None))
def get_ring_buffer(self, rx_tx):
# Configuration of RX/TX ring-buffers is not supported on every device,
# thus when it's impossible return None
if rx_tx not in ['rx', 'tx']:
ValueError('Ring-buffer type must be either "rx" or "tx"')
- return str(self._ring_buffer[0].get(rx_tx, None))
+ return str(self._ring_buffer.get(rx_tx, None))
def check_speed_duplex(self, speed, duplex):
""" Check if the passed speed and duplex combination is supported by
@@ -184,12 +176,16 @@ class Ethtool:
if duplex not in ['auto', 'full', 'half']:
raise ValueError(f'Value "{duplex}" for duplex is invalid!')
+ if speed == 'auto' and duplex == 'auto':
+ return True
+
if self.get_driver_name() in _drivers_without_speed_duplex_flow:
return False
- if speed in self._speed_duplex:
- if duplex in self._speed_duplex[speed]:
- return True
+ # ['10baset/half', '10baset/full', '100baset/half', '100baset/full', '1000baset/full']
+ tmp = [x.lower() for x in self._base_settings['supported-link-modes']]
+ if f'{speed}baset/{duplex}' in tmp:
+ return True
return False
def check_flow_control(self):
@@ -201,4 +197,4 @@ class Ethtool:
raise ValueError('Interface does not support changing '\
'flow-control settings!')
- return 'on' if bool(self._flow_control[0]['autonegotiate']) else 'off'
+ return 'on' if bool(self._flow_control['autonegotiate']) else 'off'
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index b9439d42b..34d0b73f6 100755
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -156,6 +156,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]
@@ -487,6 +501,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/frr.py b/python/vyos/frr.py
index e7743e9d5..6fb81803f 100644
--- a/python/vyos/frr.py
+++ b/python/vyos/frr.py
@@ -87,7 +87,7 @@ LOG.addHandler(ch)
LOG.addHandler(ch2)
_frr_daemons = ['zebra', 'staticd', 'bgpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd',
- 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd']
+ 'isisd', 'pimd', 'pim6d', 'ldpd', 'eigrpd', 'babeld', 'bfdd', 'fabricd']
path_vtysh = '/usr/bin/vtysh'
path_frr_reload = '/usr/lib/frr/frr-reload.py'
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index b8ea90049..8ba481728 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -504,3 +504,6 @@ class BondIf(Interface):
# call base class first
super().update(config)
+
+ # enable/disable EAPoL (Extensible Authentication Protocol over Local Area Network)
+ self.set_eapol()
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 8d96c863f..61da7b74b 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -452,3 +452,6 @@ class EthernetIf(Interface):
# call base class last
super().update(config)
+
+ # enable/disable EAPoL (Extensible Authentication Protocol over Local Area Network)
+ self.set_eapol()
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 72d3d3afe..002d3da9e 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -32,6 +32,12 @@ from vyos.configdict import list_diff
from vyos.configdict import dict_merge
from vyos.configdict import get_vlan_ids
from vyos.defaults import directories
+from vyos.pki import find_chain
+from vyos.pki import encode_certificate
+from vyos.pki import load_certificate
+from vyos.pki import wrap_private_key
+from vyos.template import is_ipv4
+from vyos.template import is_ipv6
from vyos.template import render
from vyos.utils.network import mac2eui64
from vyos.utils.dict import dict_search
@@ -41,9 +47,8 @@ from vyos.utils.network import get_vrf_tableid
from vyos.utils.network import is_netns_interface
from vyos.utils.process import is_systemd_service_active
from vyos.utils.process import run
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
from vyos.utils.file import read_file
+from vyos.utils.file import write_file
from vyos.utils.network import is_intf_addr_assigned
from vyos.utils.network import is_ipv6_link_local
from vyos.utils.assertion import assert_boolean
@@ -52,7 +57,6 @@ from vyos.utils.assertion import assert_mac
from vyos.utils.assertion import assert_mtu
from vyos.utils.assertion import assert_positive
from vyos.utils.assertion import assert_range
-
from vyos.ifconfig.control import Control
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
@@ -377,6 +381,9 @@ class Interface(Control):
>>> i = Interface('eth0')
>>> i.remove()
"""
+ # Stop WPA supplicant if EAPoL was in use
+ if is_systemd_service_active(f'wpa_supplicant-wired@{self.ifname}'):
+ self._cmd(f'systemctl stop wpa_supplicant-wired@{self.ifname}')
# remove all assigned IP addresses from interface - this is a bit redundant
# as the kernel will remove all addresses on interface deletion, but we
@@ -1522,6 +1529,61 @@ class Interface(Control):
return None
self.set_interface('per_client_thread', enable)
+ def set_eapol(self) -> None:
+ """ Take care about EAPoL supplicant daemon """
+
+ # XXX: wpa_supplicant works on the source interface
+ cfg_dir = '/run/wpa_supplicant'
+ wpa_supplicant_conf = f'{cfg_dir}/{self.ifname}.conf'
+ eapol_action='stop'
+
+ if 'eapol' in self.config:
+ # The default is a fallback to hw_id which is not present for any interface
+ # other then an ethernet interface. Thus we emulate hw_id by reading back the
+ # Kernel assigned MAC address
+ if 'hw_id' not in self.config:
+ self.config['hw_id'] = read_file(f'/sys/class/net/{self.ifname}/address')
+ render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', self.config)
+
+ cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_cert.pem')
+ cert_key_path = os.path.join(cfg_dir, f'{self.ifname}_cert.key')
+
+ cert_name = self.config['eapol']['certificate']
+ pki_cert = self.config['pki']['certificate'][cert_name]
+
+ loaded_pki_cert = load_certificate(pki_cert['certificate'])
+ loaded_ca_certs = {load_certificate(c['certificate'])
+ for c in self.config['pki']['ca'].values()} if 'ca' in self.config['pki'] else {}
+
+ cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
+
+ write_file(cert_file_path,
+ '\n'.join(encode_certificate(c) for c in cert_full_chain))
+ write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
+
+ if 'ca_certificate' in self.config['eapol']:
+ ca_cert_file_path = os.path.join(cfg_dir, f'{self.ifname}_ca.pem')
+ ca_chains = []
+
+ for ca_cert_name in self.config['eapol']['ca_certificate']:
+ pki_ca_cert = self.config['pki']['ca'][ca_cert_name]
+ loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
+ ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
+ ca_chains.append(
+ '\n'.join(encode_certificate(c) for c in ca_full_chain))
+
+ write_file(ca_cert_file_path, '\n'.join(ca_chains))
+
+ eapol_action='reload-or-restart'
+
+ # start/stop WPA supplicant service
+ self._cmd(f'systemctl {eapol_action} wpa_supplicant-wired@{self.ifname}')
+
+ if 'eapol' not in self.config:
+ # delete configuration on interface removal
+ if os.path.isfile(wpa_supplicant_conf):
+ os.unlink(wpa_supplicant_conf)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -1609,7 +1671,6 @@ class Interface(Control):
tmp = get_interface_config(config['ifname'])
if 'master' in tmp and tmp['master'] != bridge_if:
self.set_vrf('')
-
else:
self.set_vrf(config.get('vrf', ''))
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 5b5f25323..9030b1302 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -26,6 +26,7 @@ from vyos.ifconfig import Interface
from vyos.ifconfig import Operational
from vyos.template import is_ipv6
+
class WireGuardOperational(Operational):
def _dump(self):
"""Dump wireguard data in a python friendly way."""
@@ -54,7 +55,17 @@ class WireGuardOperational(Operational):
}
else:
# We are entering a peer
- device, public_key, preshared_key, endpoint, allowed_ips, latest_handshake, transfer_rx, transfer_tx, persistent_keepalive = items
+ (
+ device,
+ public_key,
+ preshared_key,
+ endpoint,
+ allowed_ips,
+ latest_handshake,
+ transfer_rx,
+ transfer_tx,
+ persistent_keepalive,
+ ) = items
if allowed_ips == '(none)':
allowed_ips = []
else:
@@ -72,75 +83,78 @@ class WireGuardOperational(Operational):
def show_interface(self):
from vyos.config import Config
+
c = Config()
wgdump = self._dump().get(self.config['ifname'], None)
- c.set_level(["interfaces", "wireguard", self.config['ifname']])
- description = c.return_effective_value(["description"])
- ips = c.return_effective_values(["address"])
+ c.set_level(['interfaces', 'wireguard', self.config['ifname']])
+ description = c.return_effective_value(['description'])
+ ips = c.return_effective_values(['address'])
- answer = "interface: {}\n".format(self.config['ifname'])
- if (description):
- answer += " description: {}\n".format(description)
- if (ips):
- answer += " address: {}\n".format(", ".join(ips))
+ answer = 'interface: {}\n'.format(self.config['ifname'])
+ if description:
+ answer += ' description: {}\n'.format(description)
+ if ips:
+ answer += ' address: {}\n'.format(', '.join(ips))
- answer += " public key: {}\n".format(wgdump['public_key'])
- answer += " private key: (hidden)\n"
- answer += " listening port: {}\n".format(wgdump['listen_port'])
- answer += "\n"
+ answer += ' public key: {}\n'.format(wgdump['public_key'])
+ answer += ' private key: (hidden)\n'
+ answer += ' listening port: {}\n'.format(wgdump['listen_port'])
+ answer += '\n'
- for peer in c.list_effective_nodes(["peer"]):
+ for peer in c.list_effective_nodes(['peer']):
if wgdump['peers']:
- pubkey = c.return_effective_value(["peer", peer, "public_key"])
+ pubkey = c.return_effective_value(['peer', peer, 'public-key'])
if pubkey in wgdump['peers']:
wgpeer = wgdump['peers'][pubkey]
- answer += " peer: {}\n".format(peer)
- answer += " public key: {}\n".format(pubkey)
+ answer += ' peer: {}\n'.format(peer)
+ answer += ' public key: {}\n'.format(pubkey)
""" figure out if the tunnel is recently active or not """
- status = "inactive"
- if (wgpeer['latest_handshake'] is None):
+ status = 'inactive'
+ if wgpeer['latest_handshake'] is None:
""" no handshake ever """
- status = "inactive"
+ status = 'inactive'
else:
if int(wgpeer['latest_handshake']) > 0:
- delta = timedelta(seconds=int(
- time.time() - wgpeer['latest_handshake']))
- answer += " latest handshake: {}\n".format(delta)
- if (time.time() - int(wgpeer['latest_handshake']) < (60*5)):
+ delta = timedelta(
+ seconds=int(time.time() - wgpeer['latest_handshake'])
+ )
+ answer += ' latest handshake: {}\n'.format(delta)
+ if time.time() - int(wgpeer['latest_handshake']) < (60 * 5):
""" Five minutes and the tunnel is still active """
- status = "active"
+ status = 'active'
else:
""" it's been longer than 5 minutes """
- status = "inactive"
+ status = 'inactive'
elif int(wgpeer['latest_handshake']) == 0:
""" no handshake ever """
- status = "inactive"
- answer += " status: {}\n".format(status)
+ status = 'inactive'
+ answer += ' status: {}\n'.format(status)
if wgpeer['endpoint'] is not None:
- answer += " endpoint: {}\n".format(wgpeer['endpoint'])
+ answer += ' endpoint: {}\n'.format(wgpeer['endpoint'])
if wgpeer['allowed_ips'] is not None:
- answer += " allowed ips: {}\n".format(
- ",".join(wgpeer['allowed_ips']).replace(",", ", "))
+ answer += ' allowed ips: {}\n'.format(
+ ','.join(wgpeer['allowed_ips']).replace(',', ', ')
+ )
if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0:
- rx_size = size(
- wgpeer['transfer_rx'], system=alternative)
- tx_size = size(
- wgpeer['transfer_tx'], system=alternative)
- answer += " transfer: {} received, {} sent\n".format(
- rx_size, tx_size)
+ rx_size = size(wgpeer['transfer_rx'], system=alternative)
+ tx_size = size(wgpeer['transfer_tx'], system=alternative)
+ answer += ' transfer: {} received, {} sent\n'.format(
+ rx_size, tx_size
+ )
if wgpeer['persistent_keepalive'] is not None:
- answer += " persistent keepalive: every {} seconds\n".format(
- wgpeer['persistent_keepalive'])
+ answer += ' persistent keepalive: every {} seconds\n'.format(
+ wgpeer['persistent_keepalive']
+ )
answer += '\n'
- return answer + super().formated_stats()
+ return answer
@Interface.register
@@ -151,27 +165,29 @@ class WireGuardIf(Interface):
**Interface.definition,
**{
'section': 'wireguard',
- 'prefixes': ['wg', ],
+ 'prefixes': [
+ 'wg',
+ ],
'bridgeable': False,
- }
+ },
}
def get_mac(self):
- """ Get a synthetic MAC address. """
+ """Get a synthetic MAC address."""
return self.get_mac_synthetic()
def update(self, config):
- """ General helper function which works on a dictionary retrived by
+ """General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
- on any interface. """
+ on any interface."""
tmp_file = NamedTemporaryFile('w')
tmp_file.write(config['private_key'])
tmp_file.flush()
# Wireguard base command is identical for every peer
- base_cmd = 'wg set {ifname}'
+ base_cmd = 'wg set {ifname}'
if 'port' in config:
base_cmd += ' listen-port {port}'
if 'fwmark' in config:
@@ -201,7 +217,7 @@ class WireGuardIf(Interface):
cmd += f' preshared-key {psk_file}'
# Persistent keepalive is optional
- if 'persistent_keepalive'in peer_config:
+ if 'persistent_keepalive' in peer_config:
cmd += ' persistent-keepalive {persistent_keepalive}'
# Multiple allowed-ip ranges can be defined - ensure we are always
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 4fe21ef13..29f8e961b 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/system/grub.py b/python/vyos/system/grub.py
index daddb799a..de8303ee2 100644
--- a/python/vyos/system/grub.py
+++ b/python/vyos/system/grub.py
@@ -82,7 +82,7 @@ def install(drive_path: str, boot_dir: str, efi_dir: str, id: str = 'VyOS', chro
f'{chroot_cmd} grub-install --no-floppy --recheck --target={efi_installation_arch}-efi \
--force-extra-removable --boot-directory={boot_dir} \
--efi-directory={efi_dir} --bootloader-id="{id}" \
- --no-uefi-secure-boot'
+ --uefi-secure-boot'
)
diff --git a/python/vyos/utils/boot.py b/python/vyos/utils/boot.py
index 3aecbec64..708bef14d 100644
--- a/python/vyos/utils/boot.py
+++ b/python/vyos/utils/boot.py
@@ -1,4 +1,4 @@
-# Copyright 2023 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023-2024 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -33,3 +33,7 @@ def boot_configuration_success() -> bool:
if int(res) == 0:
return True
return False
+
+def is_uefi_system() -> bool:
+ efi_fw_dir = '/sys/firmware/efi'
+ return os.path.exists(efi_fw_dir) and os.path.isdir(efi_fw_dir)
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/system.py b/python/vyos/utils/system.py
index fca93d118..7b12efb14 100644
--- a/python/vyos/utils/system.py
+++ b/python/vyos/utils/system.py
@@ -139,3 +139,11 @@ def get_load_averages():
res[15] = float(matches["fifteen"]) / core_count
return res
+
+def get_secure_boot_state() -> bool:
+ from vyos.utils.process import cmd
+ from vyos.utils.boot import is_uefi_system
+ if not is_uefi_system():
+ return False
+ tmp = cmd('mokutil --sb-state')
+ return bool('enabled' in tmp)
diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py
index e7e29387f..593b4b415 100644
--- a/smoketest/scripts/cli/base_interfaces_test.py
+++ b/smoketest/scripts/cli/base_interfaces_test.py
@@ -12,6 +12,8 @@
# 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 re
+
from netifaces import AF_INET
from netifaces import AF_INET6
from netifaces import ifaddresses
@@ -22,6 +24,7 @@ from vyos.configsession import ConfigSessionError
from vyos.defaults import directories
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
+from vyos.pki import CERT_BEGIN
from vyos.utils.file import read_file
from vyos.utils.dict import dict_search
from vyos.utils.process import cmd
@@ -40,6 +43,79 @@ dhclient_process_name = 'dhclient'
dhcp6c_base_dir = directories['dhcp6_client_dir']
dhcp6c_process_name = 'dhcp6c'
+server_ca_root_cert_data = """
+MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw
+HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa
+Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj
+ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK
+BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW
+a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ==
+"""
+
+server_ca_intermediate_cert_data = """
+MIIBmTCCAT+gAwIBAgIUNzrtHzLmi3QpPK57tUgCnJZhXXQwCgYIKoZIzj0EAwIw
+HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa
+Fw0zMjAyMTUxOTQxMjFaMCYxJDAiBgNVBAMMG1Z5T1Mgc2VydmVyIGludGVybWVk
+aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEl2nJ1CzoqPV6hWII2m
+eGN/uieU6wDMECTk/LgG8CCCSYb488dibUiFN/1UFsmoLIdIhkx/6MUCYh62m8U2
+WNujUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMV3YwH88I5gFsFUibbQ
+kMR0ECPsMB8GA1UdIwQYMBaAFHAL9hqJ4wSeOFhrsrcWXD+jRIW7MAoGCCqGSM49
+BAMCA0gAMEUCIQC/ahujD9dp5pMMCd3SZddqGC9cXtOwMN0JR3e5CxP13AIgIMQm
+jMYrinFoInxmX64HfshYqnUY8608nK9D2BNPOHo=
+"""
+
+client_ca_root_cert_data = """
+MIIBcDCCARagAwIBAgIUZmoW2xVdwkZSvglnkCq0AHKa6zIwCgYIKoZIzj0EAwIw
+HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa
+Fw0zMjAyMTUxOTQxMjFaMB4xHDAaBgNVBAMME1Z5T1MgY2xpZW50IHJvb3QgQ0Ew
+WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATUpKXzQk2NOVKDN4VULk2yw4mOKPvn
+mg947+VY7lbpfOfAUD0QRg95qZWCw899eKnXp/U4TkAVrmEKhUb6OJTFozIwMDAP
+BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTXu6xGWUl25X3sBtrhm3BJSICIATAK
+BggqhkjOPQQDAgNIADBFAiEAnTzEwuTI9bz2Oae3LZbjP6f/f50KFJtjLZFDbQz7
+DpYCIDNRHV8zBUibC+zg5PqMpQBKd/oPfNU76nEv6xkp/ijO
+"""
+
+client_ca_intermediate_cert_data = """
+MIIBmDCCAT+gAwIBAgIUJEMdotgqA7wU4XXJvEzDulUAGqgwCgYIKoZIzj0EAwIw
+HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjJa
+Fw0zMjAyMTUxOTQxMjJaMCYxJDAiBgNVBAMMG1Z5T1MgY2xpZW50IGludGVybWVk
+aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGyIVIi217s9j3O+WQ2b
+6R65/Z0ZjQpELxPjBRc0CA0GFCo+pI5EvwI+jNFArvTAJ5+ZdEWUJ1DQhBKDDQdI
+avCjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOUS8oNJjChB1Rb9Blcl
+ETvziHJ9MB8GA1UdIwQYMBaAFNe7rEZZSXblfewG2uGbcElIgIgBMAoGCCqGSM49
+BAMCA0cAMEQCIArhaxWgRsAUbEeNHD/ULtstLHxw/P97qPUSROLQld53AiBjgiiz
+9pDfISmpekZYz6bIDWRIR0cXUToZEMFNzNMrQg==
+"""
+
+client_cert_data = """
+MIIBmTCCAUCgAwIBAgIUV5T77XdE/tV82Tk4Vzhp5BIFFm0wCgYIKoZIzj0EAwIw
+JjEkMCIGA1UEAwwbVnlPUyBjbGllbnQgaW50ZXJtZWRpYXRlIENBMB4XDTIyMDIx
+NzE5NDEyMloXDTMyMDIxNTE5NDEyMlowIjEgMB4GA1UEAwwXVnlPUyBjbGllbnQg
+Y2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuyynqfc/qJj5e
+KJ03oOH8X4Z8spDeAPO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAh
+CIhytmJao1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTIFKrxZ+PqOhYSUqnl
+TGCUmM7wTjAfBgNVHSMEGDAWgBTlEvKDSYwoQdUW/QZXJRE784hyfTAKBggqhkjO
+PQQDAgNHADBEAiAvO8/jvz05xqmP3OXD53XhfxDLMIxzN4KPoCkFqvjlhQIgIHq2
+/geVx3rAOtSps56q/jiDouN/aw01TdpmGKVAa9U=
+"""
+
+client_key_data = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxaxAQsJwjoOCByQE
++qSYKtKtJzbdbOnTsKNSrfgkFH6hRANCAARuyynqfc/qJj5eKJ03oOH8X4Z8spDe
+APO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAhCIhytmJa
+"""
+
+def get_wpa_supplicant_value(interface, key):
+ tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
+ tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)
+ return tmp[0]
+
+def get_certificate_count(interface, cert_type):
+ tmp = read_file(f'/run/wpa_supplicant/{interface}_{cert_type}.pem')
+ return tmp.count(CERT_BEGIN)
+
def is_mirrored_to(interface, mirror_if, qdisc):
"""
Ask TC if we are mirroring traffic to a discrete interface.
@@ -57,10 +133,10 @@ def is_mirrored_to(interface, mirror_if, qdisc):
if mirror_if in tmp:
ret_val = True
return ret_val
-
class BasicInterfaceTest:
class TestCase(VyOSUnitTestSHIM.TestCase):
_test_dhcp = False
+ _test_eapol = False
_test_ip = False
_test_mtu = False
_test_vlan = False
@@ -92,6 +168,7 @@ class BasicInterfaceTest:
cls._test_vlan = cli_defined(cls._base_path, 'vif')
cls._test_qinq = cli_defined(cls._base_path, 'vif-s')
cls._test_dhcp = cli_defined(cls._base_path, 'dhcp-options')
+ cls._test_eapol = cli_defined(cls._base_path, 'eapol')
cls._test_ip = cli_defined(cls._base_path, 'ip')
cls._test_ipv6 = cli_defined(cls._base_path, 'ipv6')
cls._test_ipv6_dhcpc6 = cli_defined(cls._base_path, 'dhcpv6-options')
@@ -1158,3 +1235,86 @@ class BasicInterfaceTest:
# as until commit() is called, nothing happens
section = Section.section(delegatee)
self.cli_delete(['interfaces', section, delegatee])
+
+ def test_eapol(self):
+ if not self._test_eapol:
+ self.skipTest('not supported')
+
+ cfg_dir = '/run/wpa_supplicant'
+
+ ca_certs = {
+ 'eapol-server-ca-root': server_ca_root_cert_data,
+ 'eapol-server-ca-intermediate': server_ca_intermediate_cert_data,
+ 'eapol-client-ca-root': client_ca_root_cert_data,
+ 'eapol-client-ca-intermediate': client_ca_intermediate_cert_data,
+ }
+ cert_name = 'eapol-client'
+
+ for name, data in ca_certs.items():
+ self.cli_set(['pki', 'ca', name, 'certificate', data.replace('\n','')])
+
+ self.cli_set(['pki', 'certificate', cert_name, 'certificate', client_cert_data.replace('\n','')])
+ self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', client_key_data.replace('\n','')])
+
+ for interface in self._interfaces:
+ path = self._base_path + [interface]
+ for option in self._options.get(interface, []):
+ self.cli_set(path + option.split())
+
+ # Enable EAPoL
+ self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate'])
+ self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
+ self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name])
+
+ self.cli_commit()
+
+ # Test multiple CA chains
+ self.assertEqual(get_certificate_count(interface, 'ca'), 4)
+
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
+
+ self.cli_commit()
+
+ # Validate interface config
+ for interface in self._interfaces:
+ tmp = get_wpa_supplicant_value(interface, 'key_mgmt')
+ self.assertEqual('IEEE8021X', tmp)
+
+ tmp = get_wpa_supplicant_value(interface, 'eap')
+ self.assertEqual('TLS', tmp)
+
+ tmp = get_wpa_supplicant_value(interface, 'eapol_flags')
+ self.assertEqual('0', tmp)
+
+ tmp = get_wpa_supplicant_value(interface, 'ca_cert')
+ self.assertEqual(f'"{cfg_dir}/{interface}_ca.pem"', tmp)
+
+ tmp = get_wpa_supplicant_value(interface, 'client_cert')
+ self.assertEqual(f'"{cfg_dir}/{interface}_cert.pem"', tmp)
+
+ tmp = get_wpa_supplicant_value(interface, 'private_key')
+ self.assertEqual(f'"{cfg_dir}/{interface}_cert.key"', tmp)
+
+ mac = read_file(f'/sys/class/net/{interface}/address')
+ tmp = get_wpa_supplicant_value(interface, 'identity')
+ self.assertEqual(f'"{mac}"', tmp)
+
+ # Check certificate files have the full chain
+ self.assertEqual(get_certificate_count(interface, 'ca'), 2)
+ self.assertEqual(get_certificate_count(interface, 'cert'), 3)
+
+ # Check for running process
+ self.assertTrue(process_named_running('wpa_supplicant', cmdline=f'-i{interface}'))
+
+ # Remove EAPoL configuration
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'eapol'])
+
+ # Commit and check that process is no longer running
+ self.cli_commit()
+ self.assertFalse(process_named_running('wpa_supplicant'))
+
+ for name in ca_certs:
+ self.cli_delete(['pki', 'ca', name])
+ self.cli_delete(['pki', 'certificate', cert_name])
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_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py
index 124c1fbcb..54c981adc 100755
--- a/smoketest/scripts/cli/test_interfaces_bridge.py
+++ b/smoketest/scripts/cli/test_interfaces_bridge.py
@@ -22,6 +22,7 @@ from base_interfaces_test import BasicInterfaceTest
from copy import deepcopy
from glob import glob
+from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.template import ip_from_cidr
from vyos.utils.process import cmd
@@ -460,5 +461,38 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase):
tmp = get_interface_config(interface)
self.assertEqual(protocol, tmp['linkinfo']['info_data']['vlan_protocol'])
+ def test_bridge_delete_with_vxlan_heighbor_suppress(self):
+ vxlan_if = 'vxlan0'
+ vni = '123'
+ br_if = 'br0'
+ eth0_addr = '192.0.2.2/30'
+
+ self.cli_set(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress'])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'mtu', '1426'])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'source-address', ip_from_cidr(eth0_addr)])
+ self.cli_set(['interfaces', 'vxlan', vxlan_if, 'vni', vni])
+
+ self.cli_set(['interfaces', 'bridge', br_if, 'member', 'interface', vxlan_if])
+
+ self.cli_commit()
+
+ self.assertTrue(interface_exists(vxlan_if))
+ self.assertTrue(interface_exists(br_if))
+
+ # cannot delete bridge interface if "neighbor-suppress" parameter is configured for VXLAN interface
+ self.cli_delete(['interfaces', 'bridge', br_if])
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(['interfaces', 'vxlan', vxlan_if, 'parameters', 'neighbor-suppress'])
+
+ self.cli_commit()
+
+ self.assertFalse(interface_exists(br_if))
+
+ self.cli_delete(['interfaces', 'vxlan', vxlan_if])
+ self.cli_delete(['interfaces', 'ethernet', 'eth0', 'address', eth0_addr])
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 4843a40da..3d12364f7 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -15,7 +15,6 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import re
import unittest
from glob import glob
@@ -28,86 +27,11 @@ from netifaces import ifaddresses
from base_interfaces_test import BasicInterfaceTest
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
-from vyos.pki import CERT_BEGIN
from vyos.utils.process import cmd
-from vyos.utils.process import process_named_running
from vyos.utils.process import popen
from vyos.utils.file import read_file
from vyos.utils.network import is_ipv6_link_local
-server_ca_root_cert_data = """
-MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw
-HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa
-Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew
-WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj
-ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP
-BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK
-BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW
-a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ==
-"""
-
-server_ca_intermediate_cert_data = """
-MIIBmTCCAT+gAwIBAgIUNzrtHzLmi3QpPK57tUgCnJZhXXQwCgYIKoZIzj0EAwIw
-HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa
-Fw0zMjAyMTUxOTQxMjFaMCYxJDAiBgNVBAMMG1Z5T1Mgc2VydmVyIGludGVybWVk
-aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEl2nJ1CzoqPV6hWII2m
-eGN/uieU6wDMECTk/LgG8CCCSYb488dibUiFN/1UFsmoLIdIhkx/6MUCYh62m8U2
-WNujUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMV3YwH88I5gFsFUibbQ
-kMR0ECPsMB8GA1UdIwQYMBaAFHAL9hqJ4wSeOFhrsrcWXD+jRIW7MAoGCCqGSM49
-BAMCA0gAMEUCIQC/ahujD9dp5pMMCd3SZddqGC9cXtOwMN0JR3e5CxP13AIgIMQm
-jMYrinFoInxmX64HfshYqnUY8608nK9D2BNPOHo=
-"""
-
-client_ca_root_cert_data = """
-MIIBcDCCARagAwIBAgIUZmoW2xVdwkZSvglnkCq0AHKa6zIwCgYIKoZIzj0EAwIw
-HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjFa
-Fw0zMjAyMTUxOTQxMjFaMB4xHDAaBgNVBAMME1Z5T1MgY2xpZW50IHJvb3QgQ0Ew
-WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATUpKXzQk2NOVKDN4VULk2yw4mOKPvn
-mg947+VY7lbpfOfAUD0QRg95qZWCw899eKnXp/U4TkAVrmEKhUb6OJTFozIwMDAP
-BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTXu6xGWUl25X3sBtrhm3BJSICIATAK
-BggqhkjOPQQDAgNIADBFAiEAnTzEwuTI9bz2Oae3LZbjP6f/f50KFJtjLZFDbQz7
-DpYCIDNRHV8zBUibC+zg5PqMpQBKd/oPfNU76nEv6xkp/ijO
-"""
-
-client_ca_intermediate_cert_data = """
-MIIBmDCCAT+gAwIBAgIUJEMdotgqA7wU4XXJvEzDulUAGqgwCgYIKoZIzj0EAwIw
-HjEcMBoGA1UEAwwTVnlPUyBjbGllbnQgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjJa
-Fw0zMjAyMTUxOTQxMjJaMCYxJDAiBgNVBAMMG1Z5T1MgY2xpZW50IGludGVybWVk
-aWF0ZSBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABGyIVIi217s9j3O+WQ2b
-6R65/Z0ZjQpELxPjBRc0CA0GFCo+pI5EvwI+jNFArvTAJ5+ZdEWUJ1DQhBKDDQdI
-avCjUzBRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOUS8oNJjChB1Rb9Blcl
-ETvziHJ9MB8GA1UdIwQYMBaAFNe7rEZZSXblfewG2uGbcElIgIgBMAoGCCqGSM49
-BAMCA0cAMEQCIArhaxWgRsAUbEeNHD/ULtstLHxw/P97qPUSROLQld53AiBjgiiz
-9pDfISmpekZYz6bIDWRIR0cXUToZEMFNzNMrQg==
-"""
-
-client_cert_data = """
-MIIBmTCCAUCgAwIBAgIUV5T77XdE/tV82Tk4Vzhp5BIFFm0wCgYIKoZIzj0EAwIw
-JjEkMCIGA1UEAwwbVnlPUyBjbGllbnQgaW50ZXJtZWRpYXRlIENBMB4XDTIyMDIx
-NzE5NDEyMloXDTMyMDIxNTE5NDEyMlowIjEgMB4GA1UEAwwXVnlPUyBjbGllbnQg
-Y2VydGlmaWNhdGUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARuyynqfc/qJj5e
-KJ03oOH8X4Z8spDeAPO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAh
-CIhytmJao1AwTjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTIFKrxZ+PqOhYSUqnl
-TGCUmM7wTjAfBgNVHSMEGDAWgBTlEvKDSYwoQdUW/QZXJRE784hyfTAKBggqhkjO
-PQQDAgNHADBEAiAvO8/jvz05xqmP3OXD53XhfxDLMIxzN4KPoCkFqvjlhQIgIHq2
-/geVx3rAOtSps56q/jiDouN/aw01TdpmGKVAa9U=
-"""
-
-client_key_data = """
-MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxaxAQsJwjoOCByQE
-+qSYKtKtJzbdbOnTsKNSrfgkFH6hRANCAARuyynqfc/qJj5eKJ03oOH8X4Z8spDe
-APO9WYckMM0ldPj+9kU607szFzPwjaPWzPdgyIWz3hcN8yAhCIhytmJa
-"""
-
-def get_wpa_supplicant_value(interface, key):
- tmp = read_file(f'/run/wpa_supplicant/{interface}.conf')
- tmp = re.findall(r'\n?{}=(.*)'.format(key), tmp)
- return tmp[0]
-
-def get_certificate_count(interface, cert_type):
- tmp = read_file(f'/run/wpa_supplicant/{interface}_{cert_type}.pem')
- return tmp.count(CERT_BEGIN)
-
class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
@classmethod
def setUpClass(cls):
@@ -237,72 +161,6 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
self.cli_set(self._base_path + [interface, 'speed', 'auto'])
self.cli_commit()
- def test_eapol_support(self):
- ca_certs = {
- 'eapol-server-ca-root': server_ca_root_cert_data,
- 'eapol-server-ca-intermediate': server_ca_intermediate_cert_data,
- 'eapol-client-ca-root': client_ca_root_cert_data,
- 'eapol-client-ca-intermediate': client_ca_intermediate_cert_data,
- }
- cert_name = 'eapol-client'
-
- for name, data in ca_certs.items():
- self.cli_set(['pki', 'ca', name, 'certificate', data.replace('\n','')])
-
- self.cli_set(['pki', 'certificate', cert_name, 'certificate', client_cert_data.replace('\n','')])
- self.cli_set(['pki', 'certificate', cert_name, 'private', 'key', client_key_data.replace('\n','')])
-
- for interface in self._interfaces:
- # Enable EAPoL
- self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-server-ca-intermediate'])
- self.cli_set(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
- self.cli_set(self._base_path + [interface, 'eapol', 'certificate', cert_name])
-
- self.cli_commit()
-
- # Test multiple CA chains
- self.assertEqual(get_certificate_count(interface, 'ca'), 4)
-
- for interface in self._interfaces:
- self.cli_delete(self._base_path + [interface, 'eapol', 'ca-certificate', 'eapol-client-ca-intermediate'])
-
- self.cli_commit()
-
- # Check for running process
- self.assertTrue(process_named_running('wpa_supplicant'))
-
- # Validate interface config
- for interface in self._interfaces:
- tmp = get_wpa_supplicant_value(interface, 'key_mgmt')
- self.assertEqual('IEEE8021X', tmp)
-
- tmp = get_wpa_supplicant_value(interface, 'eap')
- self.assertEqual('TLS', tmp)
-
- tmp = get_wpa_supplicant_value(interface, 'eapol_flags')
- self.assertEqual('0', tmp)
-
- tmp = get_wpa_supplicant_value(interface, 'ca_cert')
- self.assertEqual(f'"/run/wpa_supplicant/{interface}_ca.pem"', tmp)
-
- tmp = get_wpa_supplicant_value(interface, 'client_cert')
- self.assertEqual(f'"/run/wpa_supplicant/{interface}_cert.pem"', tmp)
-
- tmp = get_wpa_supplicant_value(interface, 'private_key')
- self.assertEqual(f'"/run/wpa_supplicant/{interface}_cert.key"', tmp)
-
- mac = read_file(f'/sys/class/net/{interface}/address')
- tmp = get_wpa_supplicant_value(interface, 'identity')
- self.assertEqual(f'"{mac}"', tmp)
-
- # Check certificate files have the full chain
- self.assertEqual(get_certificate_count(interface, 'ca'), 2)
- self.assertEqual(get_certificate_count(interface, 'cert'), 3)
-
- for name in ca_certs:
- self.cli_delete(['pki', 'ca', name])
- self.cli_delete(['pki', 'certificate', cert_name])
-
def test_ethtool_ring_buffer(self):
for interface in self._interfaces:
# We do not use vyos.ethtool here to not have any chance
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_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py
new file mode 100644
index 000000000..e37aed456
--- /dev/null
+++ b/smoketest/scripts/cli/test_protocols_openfabric.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
+from vyos.utils.process import process_named_running
+
+PROCESS_NAME = 'fabricd'
+base_path = ['protocols', 'openfabric']
+
+domain = 'VyOS'
+net = '49.0001.1111.1111.1111.00'
+dummy_if = 'dum1234'
+address_families = ['ipv4', 'ipv6']
+
+path = base_path + ['domain', domain]
+
+class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ # call base-classes classmethod
+ super(TestProtocolsOpenFabric, cls).setUpClass()
+ # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same
+ cls.daemon_pid = process_named_running(PROCESS_NAME)
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # check process health and continuity
+ self.assertEqual(self.daemon_pid, process_named_running(PROCESS_NAME))
+
+ def openfabric_base_config(self):
+ self.cli_set(['interfaces', 'dummy', dummy_if])
+ self.cli_set(base_path + ['net', net])
+ for family in address_families:
+ self.cli_set(path + ['interface', dummy_if, 'address-family', family])
+
+ def test_openfabric_01_router_params(self):
+ fabric_tier = '5'
+ lsp_gen_interval = '20'
+
+ self.cli_set(base_path)
+
+ # verify() - net id and domain name are mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.openfabric_base_config()
+
+ self.cli_set(path + ['log-adjacency-changes'])
+ self.cli_set(path + ['set-overload-bit'])
+ self.cli_set(path + ['fabric-tier', fabric_tier])
+ self.cli_set(path + ['lsp-gen-interval', lsp_gen_interval])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify all changes
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' log-adjacency-changes', tmp)
+ self.assertIn(f' set-overload-bit', tmp)
+ self.assertIn(f' fabric-tier {fabric_tier}', tmp)
+ self.assertIn(f' lsp-gen-interval {lsp_gen_interval}', tmp)
+
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ self.assertIn(f' ip router openfabric {domain}', tmp)
+ self.assertIn(f' ipv6 router openfabric {domain}', tmp)
+
+ def test_openfabric_02_loopback_interface(self):
+ interface = 'lo'
+ hello_interval = '100'
+ metric = '24478'
+
+ self.openfabric_base_config()
+ self.cli_set(path + ['interface', interface, 'address-family', 'ipv4'])
+
+ self.cli_set(path + ['interface', interface, 'hello-interval', hello_interval])
+ self.cli_set(path + ['interface', interface, 'metric', metric])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify FRR openfabric configuration
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ self.assertIn(f'router openfabric {domain}', tmp)
+ self.assertIn(f' net {net}', tmp)
+
+ # Verify interface configuration
+ tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd')
+ self.assertIn(f' ip router openfabric {domain}', tmp)
+ # for lo interface 'openfabric passive' is implied
+ self.assertIn(f' openfabric passive', tmp)
+ self.assertIn(f' openfabric metric {metric}', tmp)
+
+ def test_openfabric_03_password(self):
+ password = 'foo'
+
+ self.openfabric_base_config()
+
+ self.cli_set(path + ['interface', dummy_if, 'password', 'plaintext-password', f'{password}-{dummy_if}'])
+ self.cli_set(path + ['interface', dummy_if, 'password', 'md5', f'{password}-{dummy_if}'])
+
+ # verify() - can not use both md5 and plaintext-password for password for the interface
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(path + ['interface', dummy_if, 'password', 'md5'])
+
+ self.cli_set(path + ['domain-password', 'plaintext-password', password])
+ self.cli_set(path + ['domain-password', 'md5', password])
+
+ # verify() - can not use both md5 and plaintext-password for domain-password
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(path + ['domain-password', 'md5'])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify all changes
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ self.assertIn(f' net {net}', tmp)
+ self.assertIn(f' domain-password clear {password}', tmp)
+
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp)
+
+ def test_openfabric_multiple_domains(self):
+ domain_2 = 'VyOS_2'
+ interface = 'dum5678'
+ new_path = base_path + ['domain', domain_2]
+
+ self.openfabric_base_config()
+
+ # set same interface for 2 OpenFabric domains
+ self.cli_set(['interfaces', 'dummy', interface])
+ self.cli_set(new_path + ['interface', interface, 'address-family', 'ipv4'])
+ self.cli_set(path + ['interface', interface, 'address-family', 'ipv4'])
+
+ # verify() - same interface can be used only for one OpenFabric instance
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(path + ['interface', interface])
+
+ # Commit all changes
+ self.cli_commit()
+
+ # Verify FRR openfabric configuration
+ tmp = self.getFRRconfig(f'router openfabric {domain}', daemon='fabricd')
+ self.assertIn(f'router openfabric {domain}', tmp)
+ self.assertIn(f' net {net}', tmp)
+
+ tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon='fabricd')
+ self.assertIn(f'router openfabric {domain_2}', tmp)
+ self.assertIn(f' net {net}', tmp)
+
+ # Verify interface configuration
+ tmp = self.getFRRconfig(f'interface {dummy_if}', daemon='fabricd')
+ self.assertIn(f' ip router openfabric {domain}', tmp)
+ self.assertIn(f' ipv6 router openfabric {domain}', tmp)
+
+ tmp = self.getFRRconfig(f'interface {interface}', daemon='fabricd')
+ self.assertIn(f' ip router openfabric {domain_2}', tmp)
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
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_ntp.py b/smoketest/scripts/cli/test_service_ntp.py
index ae45fe2f4..07af4f5eb 100755
--- a/smoketest/scripts/cli/test_service_ntp.py
+++ b/smoketest/scripts/cli/test_service_ntp.py
@@ -21,6 +21,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
+from vyos.xml_ref import default_value
PROCESS_NAME = 'chronyd'
NTP_CONF = '/run/chrony/chrony.conf'
@@ -165,5 +166,99 @@ class TestSystemNTP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'maxslewrate 1000', config)
self.assertIn(f'smoothtime 400 0.001024 leaponly', config)
+ def test_interleave_option(self):
+ # "interleave" option differs from some others in that the
+ # name is not a 1:1 mapping from VyOS config
+ servers = ['192.0.2.1', '192.0.2.2']
+ options = ['prefer']
+
+ for server in servers:
+ for option in options:
+ self.cli_set(base_path + ['server', server, option])
+ self.cli_set(base_path + ['server', server, 'interleave'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Check generated configuration
+ # this file must be read with higher permissions
+ config = cmd(f'sudo cat {NTP_CONF}')
+ self.assertIn('driftfile /run/chrony/drift', config)
+ self.assertIn('dumpdir /run/chrony', config)
+ self.assertIn('ntsdumpdir /run/chrony', config)
+ self.assertIn('clientloglimit 1048576', config)
+ self.assertIn('rtcsync', config)
+ self.assertIn('makestep 1.0 3', config)
+ self.assertIn('leapsectz right/UTC', config)
+
+ for server in servers:
+ self.assertIn(f'server {server} iburst ' + ' '.join(options) + ' xleave', config)
+
+ def test_offload_timestamp_default(self):
+ # Test offloading of NIC timestamp
+ servers = ['192.0.2.1', '192.0.2.2']
+ ptp_port = '8319'
+
+ for server in servers:
+ self.cli_set(base_path + ['server', server, 'ptp'])
+
+ self.cli_set(base_path + ['ptp', 'port', ptp_port])
+ self.cli_set(base_path + ['ptp', 'timestamp', 'interface', 'all'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Check generated configuration
+ # this file must be read with higher permissions
+ config = cmd(f'sudo cat {NTP_CONF}')
+ self.assertIn('driftfile /run/chrony/drift', config)
+ self.assertIn('dumpdir /run/chrony', config)
+ self.assertIn('ntsdumpdir /run/chrony', config)
+ self.assertIn('clientloglimit 1048576', config)
+ self.assertIn('rtcsync', config)
+ self.assertIn('makestep 1.0 3', config)
+ self.assertIn('leapsectz right/UTC', config)
+
+ for server in servers:
+ self.assertIn(f'server {server} iburst port {ptp_port}', config)
+
+ self.assertIn('hwtimestamp *', config)
+
+ def test_ptp_transport(self):
+ # Test offloading of NIC timestamp
+ servers = ['192.0.2.1', '192.0.2.2']
+ options = ['prefer']
+
+ default_ptp_port = default_value(base_path + ['ptp', 'port'])
+
+ for server in servers:
+ for option in options:
+ self.cli_set(base_path + ['server', server, option])
+ self.cli_set(base_path + ['server', server, 'ptp'])
+
+ # commit changes (expected to fail)
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ # add the required top-level option and commit
+ self.cli_set(base_path + ['ptp'])
+ self.cli_commit()
+
+ # Check generated configuration
+ # this file must be read with higher permissions
+ config = cmd(f'sudo cat {NTP_CONF}')
+ self.assertIn('driftfile /run/chrony/drift', config)
+ self.assertIn('dumpdir /run/chrony', config)
+ self.assertIn('ntsdumpdir /run/chrony', config)
+ self.assertIn('clientloglimit 1048576', config)
+ self.assertIn('rtcsync', config)
+ self.assertIn('makestep 1.0 3', config)
+ self.assertIn('leapsectz right/UTC', config)
+
+ for server in servers:
+ self.assertIn(f'server {server} iburst ' + ' '.join(options) + f' port {default_ptp_port}', config)
+
+ self.assertIn(f'ptpport {default_ptp_port}', config)
+
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_syslog.py b/smoketest/scripts/cli/test_system_syslog.py
index 45a5b4087..c802ceeeb 100755
--- a/smoketest/scripts/cli/test_system_syslog.py
+++ b/smoketest/scripts/cli/test_system_syslog.py
@@ -20,6 +20,7 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.utils.file import read_file
+from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
PROCESS_NAME = 'rsyslogd'
@@ -61,19 +62,45 @@ class TestRSYSLOGService(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['host', host2, 'facility', 'kern', 'level', 'err'])
self.cli_set(base_path + ['console', 'facility', 'all', 'level', 'warning'])
-
self.cli_commit()
# verify log level and facilities in config file
# *.warning /dev/console
# *.* @198.51.100.1:999
# kern.err @192.0.2.1:514
- config = [get_config_value('\*.\*'), get_config_value('kern.err'), get_config_value('\*.warning')]
+ config = [
+ get_config_value('\*.\*'),
+ get_config_value('kern.err'),
+ get_config_value('\*.warning'),
+ ]
expected = [f'@{host1}:999', f'@{host2}:514', '/dev/console']
- for i in range(0,3):
+ for i in range(0, 3):
self.assertIn(expected[i], config[i])
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ def test_syslog_global(self):
+ self.cli_set(['system', 'host-name', 'vyos'])
+ self.cli_set(['system', 'domain-name', 'example.local'])
+ self.cli_set(base_path + ['global', 'marker', 'interval', '600'])
+ self.cli_set(base_path + ['global', 'preserve-fqdn'])
+ self.cli_set(base_path + ['global', 'facility', 'kern', 'level', 'err'])
+
+ self.cli_commit()
+
+ config = cmd(f'sudo cat {RSYSLOG_CONF}')
+ expected = [
+ '$MarkMessagePeriod 600',
+ '$PreserveFQDN on',
+ 'kern.err',
+ '$LocalHostName vyos.example.local',
+ ]
+
+ for e in expected:
+ self.assertIn(e, config)
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/activation-scripts/20-ethernet_offload.py b/src/activation-scripts/20-ethernet_offload.py
index 33b0ea469..ca7213512 100755
--- a/src/activation-scripts/20-ethernet_offload.py
+++ b/src/activation-scripts/20-ethernet_offload.py
@@ -17,9 +17,12 @@
# CLI. See https://vyos.dev/T3619#102254 for all the details.
# T3787: Remove deprecated UDP fragmentation offloading option
# T6006: add to activation-scripts: migration-scripts/interfaces/20-to-21
+# T6716: Honor the configured offload settings and don't automatically add
+# them to the config if the kernel has them set (unless its a live boot)
from vyos.ethtool import Ethtool
from vyos.configtree import ConfigTree
+from vyos.system.image import is_live_boot
def activate(config: ConfigTree):
base = ['interfaces', 'ethernet']
@@ -36,7 +39,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_generic_receive_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'gro'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'gro'])
# If GSO is enabled by the Kernel - we reflect this on the CLI. If GSO is
@@ -45,7 +48,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_generic_segmentation_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'gso'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'gso'])
# If LRO is enabled by the Kernel - we reflect this on the CLI. If LRO is
@@ -54,7 +57,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_large_receive_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'lro'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'lro'])
# If SG is enabled by the Kernel - we reflect this on the CLI. If SG is
@@ -63,7 +66,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_scatter_gather()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'sg'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'sg'])
# If TSO is enabled by the Kernel - we reflect this on the CLI. If TSO is
@@ -72,7 +75,7 @@ def activate(config: ConfigTree):
enabled, fixed = eth.get_tcp_segmentation_offload()
if configured and fixed:
config.delete(base + [ifname, 'offload', 'tso'])
- elif enabled and not fixed:
+ elif is_live_boot() and enabled and not fixed:
config.set(base + [ifname, 'offload', 'tso'])
# Remove deprecated UDP fragmentation offloading option
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/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 5e5d5fba1..bbbfb0385 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -25,6 +25,7 @@ from vyos.configdict import is_source_interface
from vyos.configverify import verify_address
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_dhcpv6
+from vyos.configverify import verify_eapol
from vyos.configverify import verify_mirror_redirect
from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
@@ -73,7 +74,7 @@ def get_config(config=None):
else:
conf = Config()
base = ['interfaces', 'bonding']
- ifname, bond = get_interface_dict(conf, base)
+ ifname, bond = get_interface_dict(conf, base, with_pki=True)
# To make our own life easier transfor the list of member interfaces
# into a dictionary - we will use this to add additional information
@@ -196,6 +197,7 @@ def verify(bond):
verify_dhcpv6(bond)
verify_vrf(bond)
verify_mirror_redirect(bond)
+ verify_eapol(bond)
# use common function to verify VLAN configuration
verify_vlan_config(bond)
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 7b2c1ee0b..637db442a 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -53,20 +53,22 @@ def get_config(config=None):
tmp = node_changed(conf, base + [ifname, 'member', 'interface'])
if tmp:
if 'member' in bridge:
- bridge['member'].update({'interface_remove' : tmp })
+ bridge['member'].update({'interface_remove': {t: {} for t in tmp}})
else:
- bridge.update({'member' : {'interface_remove' : tmp }})
- for interface in tmp:
- # When using VXLAN member interfaces that are configured for Single
- # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
- # re-create VLAN to VNI mappings if required, but only if the interface
- # is already live on the system - this must not be done on first commit
- if interface.startswith('vxlan') and interface_exists(interface):
- set_dependents('vxlan', conf, interface)
- # When using Wireless member interfaces we need to inform hostapd
- # to properly set-up the bridge
- elif interface.startswith('wlan') and interface_exists(interface):
- set_dependents('wlan', conf, interface)
+ bridge.update({'member': {'interface_remove': {t: {} for t in tmp}}})
+ for interface in tmp:
+ # When using VXLAN member interfaces that are configured for Single
+ # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
+ # re-create VLAN to VNI mappings if required, but only if the interface
+ # is already live on the system - this must not be done on first commit
+ if interface.startswith('vxlan') and interface_exists(interface):
+ set_dependents('vxlan', conf, interface)
+ _, vxlan = get_interface_dict(conf, ['interfaces', 'vxlan'], ifname=interface)
+ bridge['member']['interface_remove'].update({interface: vxlan})
+ # When using Wireless member interfaces we need to inform hostapd
+ # to properly set-up the bridge
+ elif interface.startswith('wlan') and interface_exists(interface):
+ set_dependents('wlan', conf, interface)
if dict_search('member.interface', bridge) is not None:
for interface in list(bridge['member']['interface']):
@@ -118,6 +120,16 @@ def get_config(config=None):
return bridge
def verify(bridge):
+ # to delete interface or remove a member interface VXLAN first need to check if
+ # VXLAN does not require to be a member of a bridge interface
+ if dict_search('member.interface_remove', bridge):
+ for iface, iface_config in bridge['member']['interface_remove'].items():
+ if iface.startswith('vxlan') and dict_search('parameters.neighbor_suppress', iface_config) != None:
+ raise ConfigError(
+ f'To detach interface {iface} from bridge you must first '
+ f'disable "neighbor-suppress" parameter in the VXLAN interface {iface}'
+ )
+
if 'deleted' in bridge:
return None
@@ -192,7 +204,7 @@ def apply(bridge):
try:
call_dependents()
except ConfigError:
- raise ConfigError('Error updating member interface configuration after changing bridge!')
+ raise ConfigError(f'Error updating member interface {interface} configuration after changing bridge!')
return None
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index afc48ead8..34ce7bc47 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -31,32 +31,20 @@ from vyos.configverify import verify_mtu_ipv6
from vyos.configverify import verify_vlan_config
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bond_bridge_member
-from vyos.configverify import verify_pki_certificate
-from vyos.configverify import verify_pki_ca_certificate
+from vyos.configverify import verify_eapol
from vyos.ethtool import Ethtool
from vyos.ifconfig import EthernetIf
from vyos.ifconfig import BondIf
-from vyos.pki import find_chain
-from vyos.pki import encode_certificate
-from vyos.pki import load_certificate
-from vyos.pki import wrap_private_key
-from vyos.template import render
from vyos.template import render_to_string
-from vyos.utils.process import call
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
from vyos.utils.dict import dict_set
from vyos.utils.dict import dict_delete
-from vyos.utils.file import write_file
from vyos import ConfigError
from vyos import frr
from vyos import airbag
airbag.enable()
-# XXX: wpa_supplicant works on the source interface
-cfg_dir = '/run/wpa_supplicant'
-wpa_suppl_conf = '/run/wpa_supplicant/{ifname}.conf'
-
def update_bond_options(conf: Config, eth_conf: dict) -> list:
"""
Return list of blocked options if interface is a bond member
@@ -277,23 +265,6 @@ def verify_allowedbond_changes(ethernet: dict):
f' on interface "{ethernet["ifname"]}".' \
f' Interface is a bond member')
-def verify_eapol(ethernet: dict):
- """
- Common helper function used by interface implementations to perform
- recurring validation of EAPoL configuration.
- """
- if 'eapol' not in ethernet:
- return
-
- if 'certificate' not in ethernet['eapol']:
- raise ConfigError('Certificate must be specified when using EAPoL!')
-
- verify_pki_certificate(ethernet, ethernet['eapol']['certificate'], no_password_protected=True)
-
- if 'ca_certificate' in ethernet['eapol']:
- for ca_cert in ethernet['eapol']['ca_certificate']:
- verify_pki_ca_certificate(ethernet, ca_cert)
-
def verify(ethernet):
if 'deleted' in ethernet:
return None
@@ -346,51 +317,10 @@ def verify_ethernet(ethernet):
verify_vlan_config(ethernet)
return None
-
def generate(ethernet):
- # render real configuration file once
- wpa_supplicant_conf = wpa_suppl_conf.format(**ethernet)
-
if 'deleted' in ethernet:
- # delete configuration on interface removal
- if os.path.isfile(wpa_supplicant_conf):
- os.unlink(wpa_supplicant_conf)
return None
- if 'eapol' in ethernet:
- ifname = ethernet['ifname']
-
- render(wpa_supplicant_conf, 'ethernet/wpa_supplicant.conf.j2', ethernet)
-
- cert_file_path = os.path.join(cfg_dir, f'{ifname}_cert.pem')
- cert_key_path = os.path.join(cfg_dir, f'{ifname}_cert.key')
-
- cert_name = ethernet['eapol']['certificate']
- pki_cert = ethernet['pki']['certificate'][cert_name]
-
- loaded_pki_cert = load_certificate(pki_cert['certificate'])
- loaded_ca_certs = {load_certificate(c['certificate'])
- for c in ethernet['pki']['ca'].values()} if 'ca' in ethernet['pki'] else {}
-
- cert_full_chain = find_chain(loaded_pki_cert, loaded_ca_certs)
-
- write_file(cert_file_path,
- '\n'.join(encode_certificate(c) for c in cert_full_chain))
- write_file(cert_key_path, wrap_private_key(pki_cert['private']['key']))
-
- if 'ca_certificate' in ethernet['eapol']:
- ca_cert_file_path = os.path.join(cfg_dir, f'{ifname}_ca.pem')
- ca_chains = []
-
- for ca_cert_name in ethernet['eapol']['ca_certificate']:
- pki_ca_cert = ethernet['pki']['ca'][ca_cert_name]
- loaded_ca_cert = load_certificate(pki_ca_cert['certificate'])
- ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs)
- ca_chains.append(
- '\n'.join(encode_certificate(c) for c in ca_full_chain))
-
- write_file(ca_cert_file_path, '\n'.join(ca_chains))
-
ethernet['frr_zebra_config'] = ''
if 'deleted' not in ethernet:
ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet)
@@ -399,8 +329,6 @@ def generate(ethernet):
def apply(ethernet):
ifname = ethernet['ifname']
- # take care about EAPoL supplicant daemon
- eapol_action='stop'
e = EthernetIf(ifname)
if 'deleted' in ethernet:
@@ -408,10 +336,6 @@ def apply(ethernet):
e.remove()
else:
e.update(ethernet)
- if 'eapol' in ethernet:
- eapol_action='reload-or-restart'
-
- call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}')
zebra_daemon = 'zebra'
# Save original configuration prior to starting any commit actions
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/policy.py b/src/conf_mode/policy.py
index 4df893ebf..a5963e72c 100755
--- a/src/conf_mode/policy.py
+++ b/src/conf_mode/policy.py
@@ -167,10 +167,10 @@ def verify(policy):
continue
for rule, rule_config in route_map_config['rule'].items():
- # Action 'deny' cannot be used with "continue"
- # FRR does not validate it T4827
- if rule_config['action'] == 'deny' and 'continue' in rule_config:
- raise ConfigError(f'rule {rule} "continue" cannot be used with action deny!')
+ # Action 'deny' cannot be used with "continue" or "on-match"
+ # FRR does not validate it T4827, T6676
+ if rule_config['action'] == 'deny' and ('continue' in rule_config or 'on_match' in rule_config):
+ raise ConfigError(f'rule {rule} "continue" or "on-match" cannot be used with action deny!')
# Specified community-list must exist
tmp = dict_search('match.community.community_list',
diff --git a/src/conf_mode/protocols_openfabric.py b/src/conf_mode/protocols_openfabric.py
new file mode 100644
index 000000000..8e8c50c06
--- /dev/null
+++ b/src/conf_mode/protocols_openfabric.py
@@ -0,0 +1,145 @@
+#!/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/>.
+
+from sys import exit
+
+from vyos.base import Warning
+from vyos.config import Config
+from vyos.configdict import node_changed
+from vyos.configverify import verify_interface_exists
+from vyos.template import render_to_string
+from vyos import ConfigError
+from vyos import frr
+from vyos import airbag
+
+airbag.enable()
+
+def get_config(config=None):
+ if config:
+ conf = config
+ else:
+ conf = Config()
+
+ base_path = ['protocols', 'openfabric']
+
+ openfabric = conf.get_config_dict(base_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)
+
+ # Remove per domain MPLS configuration - get a list of all changed Openfabric domains
+ # (removed and added) so that they will be properly rendered for the FRR config.
+ openfabric['domains_all'] = list(conf.list_nodes(' '.join(base_path) + f' domain') +
+ node_changed(conf, base_path + ['domain']))
+
+ # Get a list of all interfaces
+ openfabric['interfaces_all'] = []
+ for domain in openfabric['domains_all']:
+ interfaces_modified = list(node_changed(conf, base_path + ['domain', domain, 'interface']) +
+ conf.list_nodes(' '.join(base_path) + f' domain {domain} interface'))
+ openfabric['interfaces_all'].extend(interfaces_modified)
+
+ if not conf.exists(base_path):
+ openfabric.update({'deleted': ''})
+
+ return openfabric
+
+def verify(openfabric):
+ # bail out early - looks like removal from running config
+ if not openfabric or 'deleted' in openfabric:
+ return None
+
+ if 'net' not in openfabric:
+ raise ConfigError('Network entity is mandatory!')
+
+ # last byte in OpenFabric area address must be 0
+ tmp = openfabric['net'].split('.')
+ if int(tmp[-1]) != 0:
+ raise ConfigError('Last byte of OpenFabric network entity title must always be 0!')
+
+ if 'domain' not in openfabric:
+ raise ConfigError('OpenFabric domain name is mandatory!')
+
+ interfaces_used = []
+
+ for domain, domain_config in openfabric['domain'].items():
+ # If interface not set
+ if 'interface' not in domain_config:
+ raise ConfigError(f'Interface used for routing updates in OpenFabric "{domain}" is mandatory!')
+
+ for iface, iface_config in domain_config['interface'].items():
+ verify_interface_exists(openfabric, iface)
+
+ # interface can be activated only on one OpenFabric instance
+ if iface in interfaces_used:
+ raise ConfigError(f'Interface {iface} is already used in different OpenFabric instance!')
+
+ if 'address_family' not in iface_config or len(iface_config['address_family']) < 1:
+ raise ConfigError(f'Need to specify address family for the interface "{iface}"!')
+
+ # If md5 and plaintext-password set at the same time
+ if 'password' in iface_config:
+ if {'md5', 'plaintext_password'} <= set(iface_config['password']):
+ raise ConfigError(f'Can use either md5 or plaintext-password for password for the interface!')
+
+ if iface == 'lo' and 'passive' not in iface_config:
+ Warning('For loopback interface passive mode is implied!')
+
+ interfaces_used.append(iface)
+
+ # If md5 and plaintext-password set at the same time
+ password = 'domain_password'
+ if password in domain_config:
+ if {'md5', 'plaintext_password'} <= set(domain_config[password]):
+ raise ConfigError(f'Can use either md5 or plaintext-password for domain-password!')
+
+ return None
+
+def generate(openfabric):
+ if not openfabric or 'deleted' in openfabric:
+ return None
+
+ openfabric['frr_fabricd_config'] = render_to_string('frr/fabricd.frr.j2', openfabric)
+ return None
+
+def apply(openfabric):
+ openfabric_daemon = 'fabricd'
+
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ frr_cfg.load_configuration(openfabric_daemon)
+ for domain in openfabric['domains_all']:
+ frr_cfg.modify_section(f'^router openfabric {domain}', stop_pattern='^exit', remove_stop_mark=True)
+
+ for interface in openfabric['interfaces_all']:
+ frr_cfg.modify_section(f'^interface {interface}', stop_pattern='^exit', remove_stop_mark=True)
+
+ if 'frr_fabricd_config' in openfabric:
+ frr_cfg.add_before(frr.default_add_before, openfabric['frr_fabricd_config'])
+
+ frr_cfg.commit_configuration(openfabric_daemon)
+
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ exit(1)
diff --git a/src/conf_mode/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/service_ntp.py b/src/conf_mode/service_ntp.py
index 83880fd72..32563aa0e 100755
--- a/src/conf_mode/service_ntp.py
+++ b/src/conf_mode/service_ntp.py
@@ -17,6 +17,7 @@
import os
from vyos.config import Config
+from vyos.config import config_dict_merge
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_interface_exists
@@ -42,13 +43,21 @@ def get_config(config=None):
if not conf.exists(base):
return None
- ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True, with_defaults=True)
+ ntp = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
ntp['config_file'] = config_file
ntp['user'] = user_group
tmp = is_node_changed(conf, base + ['vrf'])
if tmp: ntp.update({'restart_required': {}})
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ default_values = conf.get_config_defaults(**ntp.kwargs, recursive=True)
+ # Only defined PTP default port, if PTP feature is in use
+ if 'ptp' not in ntp:
+ del default_values['ptp']
+
+ ntp = config_dict_merge(default_values, ntp)
return ntp
def verify(ntp):
@@ -87,6 +96,15 @@ def verify(ntp):
if ipv6_addresses > 1:
raise ConfigError(f'NTP Only admits one ipv6 value for listen-address parameter ')
+ if 'server' in ntp:
+ for host, server in ntp['server'].items():
+ if 'ptp' in server:
+ if 'ptp' not in ntp:
+ raise ConfigError('PTP must be enabled for the NTP service '\
+ f'before it can be used for server "{host}"')
+ else:
+ break
+
return None
def generate(ntp):
diff --git a/src/conf_mode/system_option.py b/src/conf_mode/system_option.py
index 52d0b7cda..a84572f83 100755
--- a/src/conf_mode/system_option.py
+++ b/src/conf_mode/system_option.py
@@ -19,11 +19,13 @@ import os
from sys import exit
from time import sleep
+
from vyos.config import Config
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
from vyos.system import grub_util
from vyos.template import render
+from vyos.utils.cpu import get_cpus
from vyos.utils.dict import dict_search
from vyos.utils.file import write_file
from vyos.utils.kernel import check_kmod
@@ -35,6 +37,7 @@ from vyos.configdep import set_dependents
from vyos.configdep import call_dependents
from vyos import ConfigError
from vyos import airbag
+
airbag.enable()
curlrc_config = r'/etc/curlrc'
@@ -42,10 +45,8 @@ ssh_config = r'/etc/ssh/ssh_config.d/91-vyos-ssh-client-options.conf'
systemd_action_file = '/lib/systemd/system/ctrl-alt-del.target'
usb_autosuspend = r'/etc/udev/rules.d/40-usb-autosuspend.rules'
kernel_dynamic_debug = r'/sys/kernel/debug/dynamic_debug/control'
-time_format_to_locale = {
- '12-hour': 'en_US.UTF-8',
- '24-hour': 'en_GB.UTF-8'
-}
+time_format_to_locale = {'12-hour': 'en_US.UTF-8', '24-hour': 'en_GB.UTF-8'}
+
def get_config(config=None):
if config:
@@ -53,9 +54,9 @@ def get_config(config=None):
else:
conf = Config()
base = ['system', 'option']
- options = conf.get_config_dict(base, key_mangling=('-', '_'),
- get_first_key=True,
- with_recursive_defaults=True)
+ options = conf.get_config_dict(
+ base, key_mangling=('-', '_'), get_first_key=True, with_recursive_defaults=True
+ )
if 'performance' in options:
# Update IPv4/IPv6 and sysctl options after tuned applied it's settings
@@ -64,6 +65,7 @@ def get_config(config=None):
return options
+
def verify(options):
if 'http_client' in options:
config = options['http_client']
@@ -71,7 +73,9 @@ def verify(options):
verify_interface_exists(options, config['source_interface'])
if {'source_address', 'source_interface'} <= set(config):
- raise ConfigError('Can not define both HTTP source-interface and source-address')
+ raise ConfigError(
+ 'Can not define both HTTP source-interface and source-address'
+ )
if 'source_address' in config:
if not is_addr_assigned(config['source_address']):
@@ -92,10 +96,20 @@ def verify(options):
address = config['source_address']
interface = config['source_interface']
if not is_intf_addr_assigned(interface, address):
- raise ConfigError(f'Address "{address}" not assigned on interface "{interface}"!')
+ raise ConfigError(
+ f'Address "{address}" not assigned on interface "{interface}"!'
+ )
+
+ if 'kernel' in options:
+ cpu_vendor = get_cpus()[0]['vendor_id']
+ if 'amd_pstate_driver' in options['kernel'] and cpu_vendor != 'AuthenticAMD':
+ raise ConfigError(
+ f'AMD pstate driver cannot be used with "{cpu_vendor}" CPU!'
+ )
return None
+
def generate(options):
render(curlrc_config, 'system/curlrc.j2', options)
render(ssh_config, 'system/ssh_config.j2', options)
@@ -107,10 +121,16 @@ def generate(options):
cmdline_options.append('mitigations=off')
if 'disable_power_saving' in options['kernel']:
cmdline_options.append('intel_idle.max_cstate=0 processor.max_cstate=1')
+ if 'amd_pstate_driver' in options['kernel']:
+ mode = options['kernel']['amd_pstate_driver']
+ cmdline_options.append(
+ f'initcall_blacklist=acpi_cpufreq_init amd_pstate={mode}'
+ )
grub_util.update_kernel_cmdline_options(' '.join(cmdline_options))
return None
+
def apply(options):
# System bootup beep
beep_service = 'vyos-beep.service'
@@ -149,7 +169,7 @@ def apply(options):
if 'performance' in options:
cmd('systemctl restart tuned.service')
# wait until daemon has started before sending configuration
- while (not is_systemd_service_running('tuned.service')):
+ while not is_systemd_service_running('tuned.service'):
sleep(0.250)
cmd('tuned-adm profile network-{performance}'.format(**options))
else:
@@ -164,9 +184,9 @@ def apply(options):
# Enable/diable root-partition-auto-resize SystemD service
if 'root_partition_auto_resize' in options:
- cmd('systemctl enable root-partition-auto-resize.service')
+ cmd('systemctl enable root-partition-auto-resize.service')
else:
- cmd('systemctl disable root-partition-auto-resize.service')
+ cmd('systemctl disable root-partition-auto-resize.service')
# Time format 12|24-hour
if 'time_format' in options:
@@ -186,6 +206,7 @@ def apply(options):
else:
write_file(kernel_dynamic_debug, f'module {module} -p')
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/system_syslog.py b/src/conf_mode/system_syslog.py
index 07fbb0734..eb2f02eb3 100755
--- a/src/conf_mode/system_syslog.py
+++ b/src/conf_mode/system_syslog.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -18,6 +18,7 @@ import os
from sys import exit
+from vyos.base import Warning
from vyos.config import Config
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
@@ -52,12 +53,29 @@ def get_config(config=None):
if syslog.from_defaults(['global']):
del syslog['global']
+ if (
+ 'global' in syslog
+ and 'preserve_fqdn' in syslog['global']
+ and conf.exists(['system', 'host-name'])
+ and conf.exists(['system', 'domain-name'])
+ ):
+ hostname = conf.return_value(['system', 'host-name'])
+ domain = conf.return_value(['system', 'domain-name'])
+ fqdn = f'{hostname}.{domain}'
+ syslog['global']['local_host_name'] = fqdn
+
return syslog
def verify(syslog):
if not syslog:
return None
+ if 'host' in syslog:
+ for host, host_options in syslog['host'].items():
+ if 'protocol' in host_options and host_options['protocol'] == 'udp':
+ if 'format' in host_options and 'octet_counted' in host_options['format']:
+ Warning(f'Syslog UDP transport for "{host}" should not use octet-counted format!')
+
verify_vrf(syslog)
def generate(syslog):
diff --git a/src/etc/sudoers.d/vyos b/src/etc/sudoers.d/vyos
index 63a944f41..67d7babc4 100644
--- a/src/etc/sudoers.d/vyos
+++ b/src/etc/sudoers.d/vyos
@@ -57,4 +57,7 @@ Cmnd_Alias KEA_IP6_ROUTES = /sbin/ip -6 route replace *,\
# Allow members of group sudo to execute any command
%sudo ALL=NOPASSWD: ALL
+# Allow any user to query Machine Owner Key status
+%sudo ALL=NOPASSWD: /usr/bin/mokutil
+
_kea ALL=NOPASSWD: KEA_IP6_ROUTES
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/interfaces_wireguard.py b/src/op_mode/interfaces_wireguard.py
new file mode 100644
index 000000000..627af0579
--- /dev/null
+++ b/src/op_mode/interfaces_wireguard.py
@@ -0,0 +1,53 @@
+#!/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
+import vyos.opmode
+
+from vyos.ifconfig import WireGuardIf
+from vyos.configquery import ConfigTreeQuery
+
+
+def _verify(func):
+ """Decorator checks if WireGuard interface config exists"""
+ from functools import wraps
+
+ @wraps(func)
+ def _wrapper(*args, **kwargs):
+ config = ConfigTreeQuery()
+ interface = kwargs.get('intf_name')
+ if not config.exists(['interfaces', 'wireguard', interface]):
+ unconf_message = f'WireGuard interface {interface} is not configured'
+ raise vyos.opmode.UnconfiguredSubsystem(unconf_message)
+ return func(*args, **kwargs)
+
+ return _wrapper
+
+
+@_verify
+def show_summary(raw: bool, intf_name: str):
+ intf = WireGuardIf(intf_name, create=False, debug=False)
+ return intf.operational.show_interface()
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
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/op_mode/restart_frr.py b/src/op_mode/restart_frr.py
index 8841b0eca..83146f5ec 100755
--- a/src/op_mode/restart_frr.py
+++ b/src/op_mode/restart_frr.py
@@ -139,7 +139,7 @@ def _reload_config(daemon):
# define program arguments
cmd_args_parser = argparse.ArgumentParser(description='restart frr daemons')
cmd_args_parser.add_argument('--action', choices=['restart'], required=True, help='action to frr daemons')
-cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd'], required=False, nargs='*', help='select single or multiple daemons')
+cmd_args_parser.add_argument('--daemon', choices=['zebra', 'staticd', 'bgpd', 'eigrpd', 'ospfd', 'ospf6d', 'ripd', 'ripngd', 'isisd', 'pimd', 'pim6d', 'ldpd', 'babeld', 'bfdd', 'fabricd'], required=False, nargs='*', help='select single or multiple daemons')
# parse arguments
cmd_args = cmd_args_parser.parse_args()
diff --git a/src/op_mode/secure_boot.py b/src/op_mode/secure_boot.py
new file mode 100755
index 000000000..5f6390a15
--- /dev/null
+++ b/src/op_mode/secure_boot.py
@@ -0,0 +1,50 @@
+#!/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
+import vyos.opmode
+
+from vyos.utils.boot import is_uefi_system
+from vyos.utils.system import get_secure_boot_state
+
+def _get_raw_data(name=None):
+ sb_data = {
+ 'state' : get_secure_boot_state(),
+ 'uefi' : is_uefi_system()
+ }
+ return sb_data
+
+def _get_formatted_output(raw_data):
+ if not raw_data['uefi']:
+ print('System run in legacy BIOS mode!')
+ state = 'enabled' if raw_data['state'] else 'disabled'
+ return f'SecureBoot {state}'
+
+def show(raw: bool):
+ sb_data = _get_raw_data()
+ if raw:
+ return sb_data
+ else:
+ return _get_formatted_output(sb_data)
+
+if __name__ == "__main__":
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index 09d69ad1d..71a40dd50 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -25,6 +25,9 @@ import vyos.opmode
import vyos.version
import vyos.limericks
+from vyos.utils.boot import is_uefi_system
+from vyos.utils.system import get_secure_boot_state
+
from jinja2 import Template
version_output_tmpl = """
@@ -43,6 +46,7 @@ Build comment: {{build_comment}}
Architecture: {{system_arch}}
Boot via: {{boot_via}}
System type: {{system_type}}
+Secure Boot: {{secure_boot}}
Hardware vendor: {{hardware_vendor}}
Hardware model: {{hardware_model}}
@@ -57,6 +61,11 @@ Copyright: VyOS maintainers and contributors
def _get_raw_data(funny=False):
version_data = vyos.version.get_full_version_data()
+ version_data["secure_boot"] = "n/a (BIOS)"
+ if is_uefi_system():
+ version_data["secure_boot"] = "disabled"
+ if get_secure_boot_state():
+ version_data["secure_boot"] = "enabled"
if funny:
version_data["limerick"] = vyos.limericks.get_random()
diff --git a/src/op_mode/vpn_ike_sa.py b/src/op_mode/vpn_ike_sa.py
index 5e2aaae6b..9385bcd0c 100755
--- a/src/op_mode/vpn_ike_sa.py
+++ b/src/op_mode/vpn_ike_sa.py
@@ -38,6 +38,8 @@ def ike_sa(peer, nat):
peers = []
for conn in sas:
for name, sa in conn.items():
+ if peer and s(sa['remote-host']) != peer:
+ continue
if name.startswith('peer_') and name in peers:
continue
if nat and 'nat-local' not in sa:
diff --git a/src/services/vyos-configd b/src/services/vyos-configd
index 3674d9627..cb23642dc 100755
--- a/src/services/vyos-configd
+++ b/src/services/vyos-configd
@@ -14,6 +14,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# pylint: disable=redefined-outer-name
+
import os
import sys
import grp
@@ -22,9 +24,12 @@ import json
import typing
import logging
import signal
+import traceback
import importlib.util
+import io
+from contextlib import redirect_stdout
+
import zmq
-from contextlib import contextmanager
from vyos.defaults import directories
from vyos.utils.boot import boot_configuration_complete
@@ -49,7 +54,8 @@ if debug:
else:
logger.setLevel(logging.INFO)
-SOCKET_PATH = "ipc:///run/vyos-configd.sock"
+SOCKET_PATH = 'ipc:///run/vyos-configd.sock'
+MAX_MSG_SIZE = 65535
# Response error codes
R_SUCCESS = 1
@@ -64,9 +70,6 @@ configd_env_unset_file = os.path.join(directories['data'], 'vyos-configd-env-uns
# sourced on entering config session
configd_env_file = '/etc/default/vyos-configd-env'
-session_out = None
-session_mode = None
-
def key_name_from_file_name(f):
return os.path.splitext(f)[0]
@@ -76,17 +79,19 @@ def module_name_from_key(k):
def path_from_file_name(f):
return os.path.join(vyos_conf_scripts_dir, f)
+
# opt-in to be run by daemon
with open(configd_include_file) as f:
try:
include = json.load(f)
except OSError as e:
- logger.critical(f"configd include file error: {e}")
+ logger.critical(f'configd include file error: {e}')
sys.exit(1)
except json.JSONDecodeError as e:
- logger.critical(f"JSON load error: {e}")
+ logger.critical(f'JSON load error: {e}')
sys.exit(1)
+
# import conf_mode scripts
(_, _, filenames) = next(iter(os.walk(vyos_conf_scripts_dir)))
filenames.sort()
@@ -110,31 +115,17 @@ conf_mode_scripts = dict(zip(imports, modules))
exclude_set = {key_name_from_file_name(f) for f in filenames if f not in include}
include_set = {key_name_from_file_name(f) for f in filenames if f in include}
-@contextmanager
-def stdout_redirected(filename, mode):
- saved_stdout_fd = None
- destination_file = None
- try:
- sys.stdout.flush()
- saved_stdout_fd = os.dup(sys.stdout.fileno())
- destination_file = open(filename, mode)
- os.dup2(destination_file.fileno(), sys.stdout.fileno())
- yield
- finally:
- if saved_stdout_fd is not None:
- os.dup2(saved_stdout_fd, sys.stdout.fileno())
- os.close(saved_stdout_fd)
- if destination_file is not None:
- destination_file.close()
-
-def explicit_print(path, mode, msg):
- try:
- with open(path, mode) as f:
- f.write(f"\n{msg}\n\n")
- except OSError:
- logger.critical("error explicit_print")
-def run_script(script_name, config, args) -> int:
+def write_stdout_log(file_name, msg):
+ if boot_configuration_complete():
+ return
+ with open(file_name, 'a') as f:
+ f.write(msg)
+
+
+def run_script(script_name, config, args) -> tuple[int, str]:
+ # pylint: disable=broad-exception-caught
+
script = conf_mode_scripts[script_name]
script.argv = args
config.set_level([])
@@ -145,64 +136,54 @@ def run_script(script_name, config, args) -> int:
script.apply(c)
except ConfigError as e:
logger.error(e)
- explicit_print(session_out, session_mode, str(e))
- return R_ERROR_COMMIT
- except Exception as e:
- logger.critical(e)
- return R_ERROR_DAEMON
+ return R_ERROR_COMMIT, str(e)
+ except Exception:
+ tb = traceback.format_exc()
+ logger.error(tb)
+ return R_ERROR_COMMIT, tb
+
+ return R_SUCCESS, ''
- return R_SUCCESS
def initialization(socket):
- global session_out
- global session_mode
+ # pylint: disable=broad-exception-caught,too-many-locals
+
# Reset config strings:
active_string = ''
session_string = ''
# check first for resent init msg, in case of client timeout
while True:
- msg = socket.recv().decode("utf-8", "ignore")
+ msg = socket.recv().decode('utf-8', 'ignore')
try:
message = json.loads(msg)
- if message["type"] == "init":
- resp = "init"
+ if message['type'] == 'init':
+ resp = 'init'
socket.send(resp.encode())
- except:
+ except Exception:
break
# zmq synchronous for ipc from single client:
active_string = msg
- resp = "active"
+ resp = 'active'
socket.send(resp.encode())
- session_string = socket.recv().decode("utf-8", "ignore")
- resp = "session"
+ session_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'session'
socket.send(resp.encode())
- pid_string = socket.recv().decode("utf-8", "ignore")
- resp = "pid"
+ pid_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'pid'
socket.send(resp.encode())
- sudo_user_string = socket.recv().decode("utf-8", "ignore")
- resp = "sudo_user"
+ sudo_user_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'sudo_user'
socket.send(resp.encode())
- temp_config_dir_string = socket.recv().decode("utf-8", "ignore")
- resp = "temp_config_dir"
+ temp_config_dir_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'temp_config_dir'
socket.send(resp.encode())
- changes_only_dir_string = socket.recv().decode("utf-8", "ignore")
- resp = "changes_only_dir"
+ changes_only_dir_string = socket.recv().decode('utf-8', 'ignore')
+ resp = 'changes_only_dir'
socket.send(resp.encode())
- logger.debug(f"config session pid is {pid_string}")
- logger.debug(f"config session sudo_user is {sudo_user_string}")
-
- try:
- session_out = os.readlink(f"/proc/{pid_string}/fd/1")
- session_mode = 'w'
- except FileNotFoundError:
- session_out = None
-
- # if not a 'live' session, for example on boot, write to file
- if not session_out or not boot_configuration_complete():
- session_out = script_stdout_log
- session_mode = 'a'
+ logger.debug(f'config session pid is {pid_string}')
+ logger.debug(f'config session sudo_user is {sudo_user_string}')
os.environ['SUDO_USER'] = sudo_user_string
if temp_config_dir_string:
@@ -229,10 +210,12 @@ def initialization(socket):
return config
-def process_node_data(config, data, last: bool = False) -> int:
+
+def process_node_data(config, data, _last: bool = False) -> tuple[int, str]:
if not config:
- logger.critical(f"Empty config")
- return R_ERROR_DAEMON
+ out = 'Empty config'
+ logger.critical(out)
+ return R_ERROR_DAEMON, out
script_name = None
os.environ['VYOS_TAGNODE_VALUE'] = ''
@@ -246,8 +229,9 @@ def process_node_data(config, data, last: bool = False) -> int:
if res.group(2):
script_name = res.group(2)
if not script_name:
- logger.critical(f"Missing script_name")
- return R_ERROR_DAEMON
+ out = 'Missing script_name'
+ logger.critical(out)
+ return R_ERROR_DAEMON, out
if res.group(3):
args = res.group(3).split()
args.insert(0, f'{script_name}.py')
@@ -259,26 +243,55 @@ def process_node_data(config, data, last: bool = False) -> int:
scripts_called.append(script_record)
if script_name not in include_set:
- return R_PASS
+ return R_PASS, ''
+
+ with redirect_stdout(io.StringIO()) as o:
+ result, err_out = run_script(script_name, config, args)
+ amb_out = o.getvalue()
+ o.close()
+
+ out = amb_out + err_out
+
+ return result, out
+
- with stdout_redirected(session_out, session_mode):
- result = run_script(script_name, config, args)
+def send_result(sock, err, msg):
+ msg_size = min(MAX_MSG_SIZE, len(msg)) if msg else 0
+
+ err_rep = err.to_bytes(1, byteorder=sys.byteorder)
+ logger.debug(f'Sending reply: {err}')
+ sock.send(err_rep)
+
+ # size req from vyshim client
+ size_req = sock.recv().decode()
+ logger.debug(f'Received request: {size_req}')
+ msg_size_rep = hex(msg_size).encode()
+ sock.send(msg_size_rep)
+ logger.debug(f'Sending reply: {msg_size}')
+
+ if msg_size > 0:
+ # send req is sent from vyshim client only if msg_size > 0
+ send_req = sock.recv().decode()
+ logger.debug(f'Received request: {send_req}')
+ sock.send(msg.encode())
+ logger.debug('Sending reply with output')
+
+ write_stdout_log(script_stdout_log, msg)
- return result
def remove_if_file(f: str):
try:
os.remove(f)
except FileNotFoundError:
pass
- except OSError:
- raise
+
def shutdown():
remove_if_file(configd_env_file)
os.symlink(configd_env_unset_file, configd_env_file)
sys.exit(0)
+
if __name__ == '__main__':
context = zmq.Context()
socket = context.socket(zmq.REP)
@@ -294,6 +307,7 @@ if __name__ == '__main__':
os.environ['VYOS_CONFIGD'] = 't'
def sig_handler(signum, frame):
+ # pylint: disable=unused-argument
shutdown()
signal.signal(signal.SIGTERM, sig_handler)
@@ -308,20 +322,19 @@ if __name__ == '__main__':
while True:
# Wait for next request from client
msg = socket.recv().decode()
- logger.debug(f"Received message: {msg}")
+ logger.debug(f'Received message: {msg}')
message = json.loads(msg)
- if message["type"] == "init":
- resp = "init"
+ if message['type'] == 'init':
+ resp = 'init'
socket.send(resp.encode())
config = initialization(socket)
- elif message["type"] == "node":
- 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:
+ elif message['type'] == 'node':
+ res, out = process_node_data(config, message['data'], message['last'])
+ send_result(socket, res, out)
+
+ 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}")
+ logger.critical(f'Unexpected message: {message}')
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index 97633577d..91100410c 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -577,7 +577,9 @@ def _configure_op(data: Union[ConfigureModel, ConfigureListModel,
background_tasks.add_task(call_commit, session)
msg = self_ref_msg
else:
- session.commit()
+ # capture non-fatal warnings
+ out = session.commit()
+ msg = out if out else msg
logger.info(f"Configuration modified via HTTP API using key '{app.state.vyos_id}'")
except ConfigSessionError as e:
diff --git a/src/shim/vyshim.c b/src/shim/vyshim.c
index a78f62a7b..68e6c4015 100644
--- a/src/shim/vyshim.c
+++ b/src/shim/vyshim.c
@@ -67,6 +67,8 @@ void timer_handler(int);
double get_posix_clock_time(void);
+static char * s_recv_string (void *, int);
+
int main(int argc, char* argv[])
{
// string for node data: conf_mode script and tagnode, if applicable
@@ -119,31 +121,44 @@ int main(int argc, char* argv[])
zmq_recv(requester, error_code, 1, 0);
debug_print("Received node data receipt\n");
- int err = (int)error_code[0];
+ char msg_size_str[7];
+ zmq_send(requester, "msg_size", 8, 0);
+ zmq_recv(requester, msg_size_str, 6, 0);
+ msg_size_str[6] = '\0';
+ int msg_size = (int)strtol(msg_size_str, NULL, 16);
+ debug_print("msg_size: %d\n", msg_size);
+
+ if (msg_size > 0) {
+ zmq_send(requester, "send", 4, 0);
+ char *msg = s_recv_string(requester, msg_size);
+ printf("%s", msg);
+ free(msg);
+ }
free(string_node_data_msg);
- zmq_close(requester);
- zmq_ctx_destroy(context);
+ int err = (int)error_code[0];
+ int ret = 0;
if (err & PASS) {
debug_print("Received PASS\n");
- int ret = pass_through(argv, ex_index);
- return ret;
+ ret = pass_through(argv, ex_index);
}
if (err & ERROR_DAEMON) {
debug_print("Received ERROR_DAEMON\n");
- int ret = pass_through(argv, ex_index);
- return ret;
+ ret = pass_through(argv, ex_index);
}
if (err & ERROR_COMMIT) {
debug_print("Received ERROR_COMMIT\n");
- return -1;
+ ret = -1;
}
- return 0;
+ zmq_close(requester);
+ zmq_ctx_destroy(context);
+
+ return ret;
}
int initialization(void* Requester)
@@ -342,3 +357,15 @@ double get_posix_clock_time(void)
double get_posix_clock_time(void)
{return (double)0;}
#endif
+
+// Receive string from socket and convert into C string
+static char * s_recv_string (void *socket, int bufsize) {
+ char * buffer = (char *)malloc(bufsize+1);
+ int size = zmq_recv(socket, buffer, bufsize, 0);
+ if (size == -1)
+ return NULL;
+ if (size > bufsize)
+ size = bufsize;
+ buffer[size] = '\0';
+ return buffer;
+}
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
diff --git a/src/validators/interface-address b/src/validators/interface-address
index 4c203956b..2a2583fc3 100755
--- a/src/validators/interface-address
+++ b/src/validators/interface-address
@@ -1,3 +1,3 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-host $1 || ipaddrcheck --is-ipv6-host $1
+ipaddrcheck --is-any-host "$1"
diff --git a/src/validators/ip-address b/src/validators/ip-address
index 11d6df09e..351f728a6 100755
--- a/src/validators/ip-address
+++ b/src/validators/ip-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-single $1
+ipaddrcheck --is-any-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-cidr b/src/validators/ip-cidr
index 60d2ac295..8a01e7ad9 100755
--- a/src/validators/ip-cidr
+++ b/src/validators/ip-cidr
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-cidr $1
+ipaddrcheck --is-any-cidr "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP CIDR"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-host b/src/validators/ip-host
index 77c578fa2..7c5ad2612 100755
--- a/src/validators/ip-host
+++ b/src/validators/ip-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-host $1
+ipaddrcheck --is-any-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ip-prefix b/src/validators/ip-prefix
index e5a64fea8..25204ace5 100755
--- a/src/validators/ip-prefix
+++ b/src/validators/ip-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-any-net $1
+ipaddrcheck --is-any-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IP prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4 b/src/validators/ipv4
index 8676d5800..11f854cf1 100755
--- a/src/validators/ipv4
+++ b/src/validators/ipv4
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4 $1
+ipaddrcheck --is-ipv4 "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not IPv4"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-address b/src/validators/ipv4-address
index 058db088b..1cfd961ba 100755
--- a/src/validators/ipv4-address
+++ b/src/validators/ipv4-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-single $1
+ipaddrcheck --is-ipv4-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-host b/src/validators/ipv4-host
index 74b8c36a7..eb8faaa2a 100755
--- a/src/validators/ipv4-host
+++ b/src/validators/ipv4-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-host $1
+ipaddrcheck --is-ipv4-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-multicast b/src/validators/ipv4-multicast
index 3f28c51db..cf871bd59 100755
--- a/src/validators/ipv4-multicast
+++ b/src/validators/ipv4-multicast
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-multicast $1 && ipaddrcheck --is-ipv4-single $1
+ipaddrcheck --is-ipv4-multicast "$1" && ipaddrcheck --is-ipv4-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 multicast address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv4-prefix b/src/validators/ipv4-prefix
index 7e1e0e8dd..f8d46c69c 100755
--- a/src/validators/ipv4-prefix
+++ b/src/validators/ipv4-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv4-net $1
+ipaddrcheck --is-ipv4-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv4 prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6 b/src/validators/ipv6
index 4ae130eb5..57696add7 100755
--- a/src/validators/ipv6
+++ b/src/validators/ipv6
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6 $1
+ipaddrcheck --is-ipv6 "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not IPv6"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-address b/src/validators/ipv6-address
index 1fca77668..460639090 100755
--- a/src/validators/ipv6-address
+++ b/src/validators/ipv6-address
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-single $1
+ipaddrcheck --is-ipv6-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-host b/src/validators/ipv6-host
index 7085809a9..1eb4d8e35 100755
--- a/src/validators/ipv6-host
+++ b/src/validators/ipv6-host
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-host $1
+ipaddrcheck --is-ipv6-host "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 host"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-multicast b/src/validators/ipv6-multicast
index 5aa7d734a..746ff7edf 100755
--- a/src/validators/ipv6-multicast
+++ b/src/validators/ipv6-multicast
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-multicast $1 && ipaddrcheck --is-ipv6-single $1
+ipaddrcheck --is-ipv6-multicast "$1" && ipaddrcheck --is-ipv6-single "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 multicast address"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0
diff --git a/src/validators/ipv6-prefix b/src/validators/ipv6-prefix
index 890dda723..1bb9b42fe 100755
--- a/src/validators/ipv6-prefix
+++ b/src/validators/ipv6-prefix
@@ -1,10 +1,10 @@
#!/bin/sh
-ipaddrcheck --is-ipv6-net $1
+ipaddrcheck --is-ipv6-net "$1"
if [ $? -gt 0 ]; then
echo "Error: $1 is not a valid IPv6 prefix"
exit 1
fi
-exit 0 \ No newline at end of file
+exit 0