summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/trigger-rebuild-repo-package.yml33
-rw-r--r--data/templates/accel-ppp/pppoe.config.j26
-rw-r--r--data/templates/dns-forwarding/recursor.conf.lua.j228
-rw-r--r--data/templates/frr/daemons.frr.tmpl2
-rw-r--r--data/templates/frr/fabricd.frr.j272
-rw-r--r--interface-definitions/container.xml.in6
-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/protocols_openfabric.xml.in218
-rw-r--r--interface-definitions/service_dns_forwarding.xml.in173
-rw-r--r--interface-definitions/service_pppoe-server.xml.in12
-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-shell.xml.in32
-rw-r--r--op-mode-definitions/execute-wamp.xml.in (renamed from op-mode-definitions/force-wamp.xml.in)2
-rw-r--r--op-mode-definitions/force-netns.xml.in16
-rw-r--r--op-mode-definitions/force-vrf.xml.in16
-rw-r--r--op-mode-definitions/include/show-route-openfabric.xml.i8
-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-ip-route.xml.in1
-rw-r--r--op-mode-definitions/show-ipv6-route.xml.in1
-rw-r--r--op-mode-definitions/show-log.xml.in6
-rw-r--r--op-mode-definitions/show-openfabric.xml.in51
-rw-r--r--op-mode-definitions/telnet.xml.in35
-rw-r--r--op-mode-definitions/wake-on-lan.xml.in2
-rw-r--r--python/vyos/frr.py2
-rw-r--r--python/vyos/utils/convert.py62
-rwxr-xr-xsmoketest/scripts/cli/test_container.py16
-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_pppoe-server.py16
-rwxr-xr-xsrc/conf_mode/container.py4
-rw-r--r--src/conf_mode/protocols_openfabric.py145
-rwxr-xr-xsrc/conf_mode/service_dns_forwarding.py20
-rwxr-xr-xsrc/op_mode/execute_bandwidth_test.sh (renamed from src/op_mode/monitor_bandwidth_test.sh)0
-rwxr-xr-xsrc/op_mode/restart.py42
-rwxr-xr-xsrc/op_mode/restart_frr.py2
39 files changed, 1220 insertions, 114 deletions
diff --git a/.github/workflows/trigger-rebuild-repo-package.yml b/.github/workflows/trigger-rebuild-repo-package.yml
index 9c1176b01..d0936b572 100644
--- a/.github/workflows/trigger-rebuild-repo-package.yml
+++ b/.github/workflows/trigger-rebuild-repo-package.yml
@@ -9,24 +9,25 @@ on:
workflow_dispatch:
jobs:
- trigger-build:
- if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
+ get_repo_name:
runs-on: ubuntu-latest
-
- env:
- REF: main # Used for curl to trigger build package
-
+ outputs:
+ PACKAGE_NAME: ${{ steps.package_name.outputs.PACKAGE_NAME }}
steps:
- name: Set variables
+ id: package_name
run: |
- echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> $GITHUB_ENV
+ echo "PACKAGE_NAME=$(basename ${{ github.repository }})" >> $GITHUB_OUTPUT
- - name: Trigger rebuild for ${{ env.PACKAGE_NAME }}
- run: |
- curl -L \
- -X POST \
- -H "Accept: application/vnd.github+json" \
- -H "Authorization: Bearer ${{ secrets.PAT }}" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- https://api.github.com/repos/${{ secrets.REMOTE_OWNER }}/${{ secrets.REMOTE_REUSE_REPO }}/actions/workflows/build-package.yml/dispatches \
- -d '{"ref": "${{ env.REF }}", "inputs":{"package_name":"'"$PACKAGE_NAME"'", "gpg_key_id": "${{ secrets.GPG_KEY_ID }}", "package_branch": "${{ github.ref_name }}"}}'
+ trigger-build:
+ needs: get_repo_name
+ uses: vyos/.github/.github/workflows/trigger-rebuild-repo-package.yml@current
+ with:
+ branch: ${{ github.ref_name }}
+ package_name: ${{ needs.get_repo_name.outputs.PACKAGE_NAME }}
+ REF: main # optinal because the default value is main
+ secrets:
+ REMOTE_OWNER: ${{ secrets.REMOTE_OWNER }}
+ REMOTE_REUSE_REPO: ${{ secrets.REMOTE_REUSE_REPO }}
+ GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
+ PAT: ${{ secrets.PAT }}
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index beab46936..cf952c687 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -70,6 +70,12 @@ vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }}
{% if service_name %}
service-name={{ service_name | join(',') }}
{% endif %}
+{% if accept_any_service is vyos_defined %}
+accept-any-service=1
+{% endif %}
+{% if accept_blank_service is vyos_defined %}
+accept-blank-service=1
+{% endif %}
{% if pado_delay %}
{% set delay_without_sessions = pado_delay.delays_without_sessions[0] | default('0') %}
{% set pado_delay_param = namespace(value=delay_without_sessions) %}
diff --git a/data/templates/dns-forwarding/recursor.conf.lua.j2 b/data/templates/dns-forwarding/recursor.conf.lua.j2
index 8026442c7..622283ad8 100644
--- a/data/templates/dns-forwarding/recursor.conf.lua.j2
+++ b/data/templates/dns-forwarding/recursor.conf.lua.j2
@@ -6,3 +6,31 @@ dofile("/usr/share/pdns-recursor/lua-config/rootkeys.lua")
-- Load lua from vyos-hostsd --
dofile("{{ config_dir }}/recursor.vyos-hostsd.conf.lua")
+
+-- ZoneToCache --
+{% if zone_cache is vyos_defined %}
+{% set option_mapping = {
+ 'refresh': 'refreshPeriod',
+ 'retry_interval': 'retryOnErrorPeriod',
+ 'max_zone_size': 'maxReceivedMBytes'
+} %}
+{% for name, conf in zone_cache.items() %}
+{% set source = conf.source.items() | first %}
+{% set settings = [] %}
+{% for key, val in conf.options.items() %}
+{% set mapped_key = option_mapping.get(key, key) %}
+{% if key == 'refresh' %}
+{% set val = val['interval'] %}
+{% endif %}
+{% if key in ['dnssec', 'zonemd'] %}
+{% set _ = settings.append(mapped_key ~ ' = "' ~ val ~ '"') %}
+{% else %}
+{% set _ = settings.append(mapped_key ~ ' = ' ~ val) %}
+{% endif %}
+{% endfor %}
+
+zoneToCache("{{ name }}", "{{ source[0] }}", "{{ source[1] }}", { {{ settings | join(', ') }} })
+
+{% endfor %}
+
+{% endif %}
diff --git a/data/templates/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/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/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/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_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/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-shell.xml.in b/op-mode-definitions/execute-shell.xml.in
new file mode 100644
index 000000000..dfdc1e371
--- /dev/null
+++ b/op-mode-definitions/execute-shell.xml.in
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="execute">
+ <children>
+ <node name="shell">
+ <properties>
+ <help>Execute shell</help>
+ </properties>
+ <children>
+ <tagNode name="netns">
+ <properties>
+ <help>Execute shell in given Network Namespace</help>
+ <completionHelp>
+ <path>netns name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ip netns exec $4 su - $(whoami)</command>
+ </tagNode>
+ <tagNode name="vrf">
+ <properties>
+ <help>Execute shell in given VRF instance</help>
+ <completionHelp>
+ <path>vrf name</path>
+ </completionHelp>
+ </properties>
+ <command>sudo ip vrf exec $4 su - $(whoami)</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/force-wamp.xml.in b/op-mode-definitions/execute-wamp.xml.in
index dbb205c6b..bcceedc53 100644
--- a/op-mode-definitions/force-wamp.xml.in
+++ b/op-mode-definitions/execute-wamp.xml.in
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
<interfaceDefinition>
- <node name="force">
+ <node name="execute">
<children>
<tagNode name="owping">
<properties>
diff --git a/op-mode-definitions/force-netns.xml.in b/op-mode-definitions/force-netns.xml.in
deleted file mode 100644
index b9dc2c1e8..000000000
--- a/op-mode-definitions/force-netns.xml.in
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="force">
- <children>
- <tagNode name="netns">
- <properties>
- <help>Execute shell in given Network Namespace</help>
- <completionHelp>
- <path>netns name</path>
- </completionHelp>
- </properties>
- <command>sudo ip netns exec $3 su - $(whoami)</command>
- </tagNode>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/force-vrf.xml.in b/op-mode-definitions/force-vrf.xml.in
deleted file mode 100644
index 71f50b0d2..000000000
--- a/op-mode-definitions/force-vrf.xml.in
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="force">
- <children>
- <tagNode name="vrf">
- <properties>
- <help>Execute shell in given VRF instance</help>
- <completionHelp>
- <path>vrf name</path>
- </completionHelp>
- </properties>
- <command>sudo ip vrf exec $3 su - $(whoami)</command>
- </tagNode>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/op-mode-definitions/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/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-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..f0fad63d2 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -642,6 +642,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/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 625cf4056..d4589c868 100644
--- a/op-mode-definitions/wake-on-lan.xml.in
+++ b/op-mode-definitions/wake-on-lan.xml.in
@@ -19,7 +19,7 @@
<properties>
<help>Station (MAC) address to wake up</help>
</properties>
- <command>sudo /usr/sbin/etherwake -i "$3" "$5"</command>
+ <command>sudo /usr/sbin/etherwake -i "$4" "$6"</command>
</tagNode>
</children>
</tagNode>
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/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/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 5e33eba40..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'
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_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/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/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/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/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()