diff options
42 files changed, 1307 insertions, 126 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/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/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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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/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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></list> + </completionHelp> + </properties> + <command>/usr/bin/ssh $4</command> + <children> + <tagNode name="user"> + <properties> + <help>Remote server username</help> + <completionHelp> + <list><username></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/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><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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><0-65535></list> + <list><hostname> <x.x.x.x> <h:h:h:h:h:h:h:h></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><0-65535></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/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/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() |