diff options
86 files changed, 1569 insertions, 657 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cd348ead7..caabab3d9 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ <!-- All PR should follow this template to allow a clean and transparent review --> <!-- Text placed between these delimiters is considered a comment and is not rendered --> -## Change Summary +## Change summary <!--- Provide a general summary of your changes in the Title above --> ## Types of changes @@ -24,13 +24,7 @@ the box, please use [x] ## Related PR(s) <!-- Link here any PRs in other repositories that are required by this PR --> -## Component(s) name -<!-- A rather incomplete list of components: ethernet, wireguard, bgp, mpls, ldp, l2tp, dhcp ... --> - -## Proposed changes -<!--- Describe your changes in detail --> - -## How to test +## How to test / Smoketest result <!--- Please describe in detail how you tested your changes. Include details of your testing environment, and the tests you ran. When pasting configs, logs, shell output, backtraces, @@ -38,10 +32,9 @@ and other large chunks of text, surround this text with triple backtics ``` like this ``` ---> -## Smoketest result -<!-- Provide the output of the smoketest +Or provide the output of the smoketest + ``` $ /usr/libexec/vyos/tests/smoke/cli/test_xxx_feature.py test_01_simple_options (__main__.TestFeature.test_01_simple_options) ... ok diff --git a/.github/workflows/cleanup-mirror-pr-branch.yml b/.github/workflows/cleanup-mirror-pr-branch.yml index c5de9ab73..bbe6aa2f2 100644 --- a/.github/workflows/cleanup-mirror-pr-branch.yml +++ b/.github/workflows/cleanup-mirror-pr-branch.yml @@ -5,31 +5,11 @@ on: types: [closed] branches: - current - workflow_dispatch: - inputs: - branch: - description: 'Branch to delete' - required: true permissions: contents: write jobs: - delete_branch: - if: ${{ (github.event_name == 'workflow_dispatch' || startsWith(github.event.pull_request.head.ref, 'mirror/')) && github.repository_owner != 'vyos' }} - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Delete branch - run: | - branch=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch || github.event.pull_request.head.ref }} - if [[ $branch != mirror/* ]]; then - echo "Branch name to clean must start with 'mirror/'" - exit 1 - fi - repo=${{ github.repository }} - git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} - git push origin --delete $branch + call-delete-branch: + uses: vyos/.github/.github/workflows/cleanup-mirror-pr-branch.yml@current + secrets: inherit diff --git a/.github/workflows/package-smoketest.yml b/.github/workflows/package-smoketest.yml index d352bd3cb..ae34ea2f0 100644 --- a/.github/workflows/package-smoketest.yml +++ b/.github/workflows/package-smoketest.yml @@ -64,7 +64,9 @@ jobs: - uses: actions/upload-artifact@v4 with: name: vyos-${{ steps.version.outputs.build_version }} - path: build/live-image-amd64.hybrid.iso + path: | + build/live-image-amd64.hybrid.iso + build/manifest.json test_smoketest_cli: needs: build_iso diff --git a/.github/workflows/trigger-pr-mirror-repo-sync.yml b/.github/workflows/trigger-pr-mirror-repo-sync.yml index 9653c2dca..d5e8ce3b4 100644 --- a/.github/workflows/trigger-pr-mirror-repo-sync.yml +++ b/.github/workflows/trigger-pr-mirror-repo-sync.yml @@ -6,33 +6,7 @@ on: branches: - current -env: - GH_TOKEN: ${{ secrets.PAT }} - -concurrency: - group: trigger-pr-mirror-repo-sync-${{ github.event.pull_request.base.ref }} - cancel-in-progress: false jobs: - trigger-mirror-pr-repo-sync: - if: ${{ github.repository_owner == 'vyos' }} - runs-on: ubuntu-latest - permissions: - pull-requests: write - contents: write - - steps: - - name: Bullfrog Secure Runner - uses: bullfrogsec/bullfrog@v0 - with: - egress-policy: audit - - - name: Trigger repo sync - shell: bash - run: | - echo "Triggering sync workflow for ${{ secrets.REMOTE_OWNER }}/${{ secrets.REMOTE_REPO }}" - echo "Triggering sync workflow with PAT ${{ secrets.PAT }}" - curl -X POST \ - -H "Accept: application/vnd.github.everest-preview+json" \ - -H "Authorization: Bearer ${{ secrets.PAT }}" \ - https://api.github.com/repos/${{ secrets.REMOTE_OWNER }}/${{ secrets.REMOTE_REPO }}/actions/workflows/mirror-pr-and-sync.yml/dispatches \ - -d '{"ref":"git-actions", "inputs": {"pr_number": "${{ github.event.pull_request.number }}", "sync_branch": "${{ github.event.pull_request.base.ref }}"}}' + call-trigger-mirror-pr-repo-sync: + uses: vyos/.github/.github/workflows/trigger-pr-mirror-repo-sync.yml@current + secrets: inherit diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2 index cf952c687..2c4871a6b 100644 --- a/data/templates/accel-ppp/pppoe.config.j2 +++ b/data/templates/accel-ppp/pppoe.config.j2 @@ -61,6 +61,9 @@ interface={{ iface }} {% for vlan in iface_config.vlan %} interface=re:^{{ iface }}\.{{ vlan | range_to_regex }}$ {% endfor %} +{% if iface_config.combined is vyos_defined %} +interface={{ iface }} +{% endif %} {% if iface_config.vlan_mon is vyos_defined %} vlan-mon={{ iface }},{{ iface_config.vlan | join(',') }} {% endif %} diff --git a/data/templates/dns-dynamic/ddclient.conf.j2 b/data/templates/dns-dynamic/ddclient.conf.j2 index 5538ea56c..23aad4cb8 100644 --- a/data/templates/dns-dynamic/ddclient.conf.j2 +++ b/data/templates/dns-dynamic/ddclient.conf.j2 @@ -24,7 +24,6 @@ if{{ ipv }}={{ address }}, \ daemon={{ interval }} syslog=yes ssl=yes -pid={{ config_file | replace('.conf', '.pid') }} cache={{ config_file | replace('.conf', '.cache') }} {# ddclient default (web=dyndns) doesn't support ssl and results in process lockup #} web=googledomains diff --git a/data/templates/dns-dynamic/override.conf.j2 b/data/templates/dns-dynamic/override.conf.j2 index 4a6851cef..c0edd8f05 100644 --- a/data/templates/dns-dynamic/override.conf.j2 +++ b/data/templates/dns-dynamic/override.conf.j2 @@ -1,10 +1,11 @@ {% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %} [Unit] ConditionPathExists={{ config_file }} +Wants= After=vyos-router.service [Service] -PIDFile={{ config_file | replace('.conf', '.pid') }} EnvironmentFile= ExecStart= -ExecStart={{ vrf_command }}/usr/bin/ddclient -file {{ config_file }} +ExecStart={{ vrf_command }}/usr/bin/ddclient --file {{ config_file }} --foreground +Restart=always diff --git a/data/templates/firewall/nftables-zone.j2 b/data/templates/firewall/nftables-zone.j2 index e78725079..645a38706 100644 --- a/data/templates/firewall/nftables-zone.j2 +++ b/data/templates/firewall/nftables-zone.j2 @@ -8,7 +8,14 @@ {% endif %} {% for zone_name, zone_conf in zone.items() %} {% if 'local_zone' not in zone_conf %} - oifname { {{ zone_conf.interface | join(',') }} } counter jump VZONE_{{ zone_name }} +{% if 'interface' in zone_conf.member %} + oifname { {{ zone_conf.member.interface | join(',') }} } counter jump VZONE_{{ zone_name }} +{% endif %} +{% if 'vrf' in zone_conf.member %} +{% for vrf_name in zone_conf.member.vrf %} + oifname { {{ zone_conf['vrf_interfaces'][vrf_name] }} } counter jump VZONE_{{ zone_name }} +{% endfor %} +{% endif %} {% endif %} {% endfor %} } @@ -40,8 +47,15 @@ iifname lo counter return {% if zone_conf.from is vyos_defined %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return + +{% if 'interface' in zone[from_zone].member %} + iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return +{% endif %} +{% if 'vrf' in zone[from_zone].member %} + iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return +{% endif %} {% endfor %} {% endif %} {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} @@ -50,23 +64,47 @@ oifname lo counter return {% if zone_conf.from_local is vyos_defined %} {% for from_zone, from_conf in zone_conf.from_local.items() if from_conf.firewall[fw_name] is vyos_defined %} - oifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - oifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% if 'interface' in zone[from_zone].member %} + oifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + oifname { {{ zone[from_zone].member.interface | join(",") }} } counter return +{% endif %} +{% if 'vrf' in zone[from_zone].member %} +{% for vrf_name in zone[from_zone].member.vrf %} + oifname { {{ zone[from_zone]['vrf_interfaces'][vrf_name] }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + oifname { {{ zone[from_zone]['vrf_interfaces'][vrf_name] }} } counter return +{% endfor %} +{% endif %} {% endfor %} {% endif %} {{ zone_conf | nft_default_rule('zone_' + zone_name, family) }} } {% else %} chain VZONE_{{ zone_name }} { - iifname { {{ zone_conf.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }} +{% if 'interface' in zone_conf.member %} + iifname { {{ zone_conf.member.interface | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }} +{% endif %} +{% if 'vrf' in zone_conf.member %} + iifname { {{ zone_conf.member.vrf | join(",") }} } counter {{ zone_conf | nft_intra_zone_action(ipv6) }} +{% endif %} {% if zone_conf.intra_zone_filtering is vyos_defined %} - iifname { {{ zone_conf.interface | join(",") }} } counter return +{% if 'interface' in zone_conf.member %} + iifname { {{ zone_conf.member.interface | join(",") }} } counter return +{% endif %} +{% if 'vrf' in zone_conf.member %} + iifname { {{ zone_conf.member.vrf | join(",") }} } counter return +{% endif %} {% endif %} {% if zone_conf.from is vyos_defined %} {% for from_zone, from_conf in zone_conf.from.items() if from_conf.firewall[fw_name] is vyos_defined %} {% if zone[from_zone].local_zone is not defined %} - iifname { {{ zone[from_zone].interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} - iifname { {{ zone[from_zone].interface | join(",") }} } counter return +{% if 'interface' in zone[from_zone].member %} + iifname { {{ zone[from_zone].member.interface | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].member.interface | join(",") }} } counter return +{% endif %} +{% if 'vrf' in zone[from_zone].member %} + iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter jump NAME{{ suffix }}_{{ from_conf.firewall[fw_name] }} + iifname { {{ zone[from_zone].member.vrf | join(",") }} } counter return +{% endif %} {% endif %} {% endfor %} {% endif %} diff --git a/data/templates/frr/zebra.segment_routing.frr.j2 b/data/templates/frr/zebra.segment_routing.frr.j2 index 7b12fcdd0..718d47d8f 100644 --- a/data/templates/frr/zebra.segment_routing.frr.j2 +++ b/data/templates/frr/zebra.segment_routing.frr.j2 @@ -11,6 +11,9 @@ segment-routing {% if locator_config.behavior_usid is vyos_defined %} behavior usid {% endif %} +{% if locator_config.format is vyos_defined %} + format {{ locator_config.format }} +{% endif %} exit ! {% endfor %} diff --git a/data/templates/prometheus/blackbox_exporter.service.j2 b/data/templates/prometheus/blackbox_exporter.service.j2 new file mode 100644 index 000000000..e93030246 --- /dev/null +++ b/data/templates/prometheus/blackbox_exporter.service.j2 @@ -0,0 +1,21 @@ +{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' runuser -u node_exporter -- ' if vrf is vyos_defined else '' %} +[Unit] +Description=Blackbox Exporter +Documentation=https://github.com/prometheus/blackbox_exporter +After=network.target + +[Service] +{% if vrf is not vyos_defined %} +User=node_exporter +{% endif %} +ExecStart={{ vrf_command }}/usr/sbin/blackbox_exporter \ +{% if listen_address is vyos_defined %} +{% for address in listen_address %} + --web.listen-address={{ address }}:{{ port }} \ +{% endfor %} +{% else %} + --web.listen-address=:{{ port }} \ +{% endif %} + --config.file=/run/blackbox_exporter/config.yml +[Install] +WantedBy=multi-user.target diff --git a/data/templates/prometheus/blackbox_exporter.yml.j2 b/data/templates/prometheus/blackbox_exporter.yml.j2 new file mode 100644 index 000000000..ba2eecd77 --- /dev/null +++ b/data/templates/prometheus/blackbox_exporter.yml.j2 @@ -0,0 +1,23 @@ +modules: +{% if modules is defined and modules.dns is defined and modules.dns.name is defined %} +{% for module_name, module_config in modules.dns.name.items() %} + {{ module_name }}: + prober: dns + timeout: {{ module_config.timeout }}s + dns: + query_name: "{{ module_config.query_name }}" + query_type: "{{ module_config.query_type }}" + preferred_ip_protocol: "{{ module_config.preferred_ip_protocol | replace('v', '') }}" + ip_protocol_fallback: {{ 'true' if module_config.ip_protocol_fallback is vyos_defined else 'false' }} +{% endfor %} +{% endif %} +{% if modules is defined and modules.icmp is vyos_defined and modules.icmp.name is vyos_defined %} +{% for module_name, module_config in modules.icmp.name.items() %} + {{ module_name }}: + prober: icmp + timeout: {{ module_config.timeout }}s + icmp: + preferred_ip_protocol: "{{ module_config.preferred_ip_protocol | replace('v', '') }}" + ip_protocol_fallback: {{ 'true' if module_config.ip_protocol_fallback is vyos_defined else 'false' }} +{% endfor %} +{% endif %}
\ No newline at end of file diff --git a/data/templates/prometheus/node_exporter.service.j2 b/data/templates/prometheus/node_exporter.service.j2 index 62e7e6774..135439bd6 100644 --- a/data/templates/prometheus/node_exporter.service.j2 +++ b/data/templates/prometheus/node_exporter.service.j2 @@ -16,5 +16,10 @@ ExecStart={{ vrf_command }}/usr/sbin/node_exporter \ {% else %} --web.listen-address=:{{ port }} {% endif %} +{% if collectors is vyos_defined %} +{% if collectors.textfile is vyos_defined %} + --collector.textfile.directory=/run/node_exporter/collector +{% endif %} +{% endif %} [Install] WantedBy=multi-user.target diff --git a/data/templates/ssh/sshd_config.j2 b/data/templates/ssh/sshd_config.j2 index 2cf0494c4..7e44efae8 100644 --- a/data/templates/ssh/sshd_config.j2 +++ b/data/templates/ssh/sshd_config.j2 @@ -110,3 +110,7 @@ ClientAliveInterval {{ client_keepalive_interval }} {% if rekey.data is vyos_defined %} RekeyLimit {{ rekey.data }}M {{ rekey.time + 'M' if rekey.time is vyos_defined }} {% endif %} + +{% if trusted_user_ca_key is vyos_defined %} +TrustedUserCAKeys /etc/ssh/trusted_user_ca_key +{% endif %} diff --git a/data/templates/telegraf/telegraf.j2 b/data/templates/telegraf/telegraf.j2 index 535e3a347..043fc6878 100644 --- a/data/templates/telegraf/telegraf.j2 +++ b/data/templates/telegraf/telegraf.j2 @@ -52,7 +52,7 @@ password = "{{ loki.authentication.password }}" {% endif %} {% if loki.metric_name_label is vyos_defined %} -metric_name_label = "{{ loki.metric_name_label }}" + metric_name_label = "{{ loki.metric_name_label }}" {% endif %} ### End Loki ### {% endif %} diff --git a/debian/control b/debian/control index 08b86356a..a0d475d56 100644 --- a/debian/control +++ b/debian/control @@ -40,7 +40,8 @@ Pre-Depends: libpam-runtime [amd64], libnss-tacplus [amd64], libpam-tacplus [amd64], - libpam-radius-auth [amd64] + libpam-radius-auth (= 1.5.0-cl3u7) [amd64], + libnss-mapuser (= 1.1.0-cl3u3) [amd64] Depends: ## Fundamentals ${python3:Depends} (>= 3.10), @@ -241,6 +242,9 @@ Depends: # For "service monitoring prometheus frr-exporter" frr-exporter, # End "service monitoring prometheus frr-exporter" +# For "service monitoring prometheus blackbox-exporter" + blackbox-exporter, +# End "service monitoring prometheus blackbox-exporter" # For "service monitoring telegraf" telegraf (>= 1.20), # End "service monitoring telegraf" diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install index d5dd3bcec..5fcff959a 100644 --- a/debian/vyos-1x.install +++ b/debian/vyos-1x.install @@ -1,5 +1,6 @@ etc/bash_completion.d etc/commit +etc/cron.d etc/default etc/dhcp etc/ipsec.d diff --git a/interface-definitions/firewall.xml.in b/interface-definitions/firewall.xml.in index 07c88f799..e4fe9a508 100755..100644 --- a/interface-definitions/firewall.xml.in +++ b/interface-definitions/firewall.xml.in @@ -16,15 +16,7 @@ </properties> <children> #include <include/generic-description.xml.i> - <leafNode name="interface"> - <properties> - <help>Interfaces to use this flowtable</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <multi/> - </properties> - </leafNode> + #include <include/generic-interface-multi.xml.i> <leafNode name="offload"> <properties> <help>Offloading method</help> @@ -155,15 +147,7 @@ <constraintErrorMessage>Name of firewall group can only contain alphanumeric letters, hyphen, underscores and dot</constraintErrorMessage> </properties> <children> - <leafNode name="interface"> - <properties> - <help>Interface-group member</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <multi/> - </properties> - </leafNode> + #include <include/generic-interface-multi.xml.i> <leafNode name="include"> <properties> <help>Include another interface-group</help> @@ -464,24 +448,27 @@ </node> </children> </tagNode> - <leafNode name="interface"> + <node name="member"> <properties> <help>Interface associated with zone</help> - <valueHelp> - <format>txt</format> - <description>Interface associated with zone</description> - </valueHelp> - <valueHelp> - <format>vrf</format> - <description>VRF associated with zone</description> - </valueHelp> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - <path>vrf name</path> - </completionHelp> - <multi/> </properties> - </leafNode> + <children> + #include <include/generic-interface-multi.xml.i> + <leafNode name="vrf"> + <properties> + <help>VRF associated with zone</help> + <valueHelp> + <format>vrf</format> + <description>VRF associated with zone</description> + </valueHelp> + <completionHelp> + <path>vrf name</path> + </completionHelp> + <multi/> + </properties> + </leafNode> + </children> + </node> <node name="intra-zone-filtering"> <properties> <help>Intra-zone filtering</help> diff --git a/interface-definitions/high-availability.xml.in b/interface-definitions/high-availability.xml.in index 7108aa06c..6cf6237ca 100644 --- a/interface-definitions/high-availability.xml.in +++ b/interface-definitions/high-availability.xml.in @@ -247,22 +247,7 @@ <help>Disable track state of main interface</help> </properties> </leafNode> - <leafNode name="interface"> - <properties> - <help>Interface name state check</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces --broadcast</script> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Interface name</description> - </valueHelp> - <constraint> - #include <include/constraint/interface-name.xml.i> - </constraint> - <multi/> - </properties> - </leafNode> + #include <include/generic-interface-multi-broadcast.xml.i> </children> </node> #include <include/vrrp-transition-script.xml.i> diff --git a/interface-definitions/include/bgp/protocol-common-config.xml.i b/interface-definitions/include/bgp/protocol-common-config.xml.i index 0f05625a7..4953251c5 100644 --- a/interface-definitions/include/bgp/protocol-common-config.xml.i +++ b/interface-definitions/include/bgp/protocol-common-config.xml.i @@ -721,15 +721,7 @@ <help>Apply local policy routing to interface</help> </properties> <children> - <leafNode name="interface"> - <properties> - <help>Interface</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <multi/> - </properties> - </leafNode> + #include <include/generic-interface-multi.xml.i> </children> </node> </children> diff --git a/interface-definitions/include/generic-interface-broadcast.xml.i b/interface-definitions/include/generic-interface-broadcast.xml.i index e37e75012..52a4a2717 100644 --- a/interface-definitions/include/generic-interface-broadcast.xml.i +++ b/interface-definitions/include/generic-interface-broadcast.xml.i @@ -1,7 +1,7 @@ <!-- include start from generic-interface-broadcast.xml.i --> <leafNode name="interface"> <properties> - <help>Interface to use</help> + <help>Interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces --broadcast</script> </completionHelp> diff --git a/interface-definitions/include/generic-interface-multi-broadcast.xml.i b/interface-definitions/include/generic-interface-multi-broadcast.xml.i index ed13cf2cf..65ca1ffab 100644 --- a/interface-definitions/include/generic-interface-multi-broadcast.xml.i +++ b/interface-definitions/include/generic-interface-multi-broadcast.xml.i @@ -1,7 +1,7 @@ <!-- include start from generic-interface-multi-broadcast.xml.i --> <leafNode name="interface"> <properties> - <help>Interface to use</help> + <help>Interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces --broadcast</script> </completionHelp> diff --git a/interface-definitions/include/generic-interface-multi-wildcard.xml.i b/interface-definitions/include/generic-interface-multi-wildcard.xml.i index 6c846a795..cd65028ac 100644 --- a/interface-definitions/include/generic-interface-multi-wildcard.xml.i +++ b/interface-definitions/include/generic-interface-multi-wildcard.xml.i @@ -1,7 +1,7 @@ <!-- include start from generic-interface-multi-wildcard.xml.i --> <leafNode name="interface"> <properties> - <help>Interface to use</help> + <help>Interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> diff --git a/interface-definitions/include/generic-interface-multi.xml.i b/interface-definitions/include/generic-interface-multi.xml.i index cfc77af3a..a4329cba7 100644 --- a/interface-definitions/include/generic-interface-multi.xml.i +++ b/interface-definitions/include/generic-interface-multi.xml.i @@ -1,7 +1,7 @@ <!-- include start from generic-interface-multi.xml.i --> <leafNode name="interface"> <properties> - <help>Interface to use</help> + <help>Interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> diff --git a/interface-definitions/include/generic-interface.xml.i b/interface-definitions/include/generic-interface.xml.i index 65f5bfbb8..cf6fb9151 100644 --- a/interface-definitions/include/generic-interface.xml.i +++ b/interface-definitions/include/generic-interface.xml.i @@ -1,7 +1,7 @@ <!-- include start from generic-interface.xml.i --> <leafNode name="interface"> <properties> - <help>Interface to use</help> + <help>Interface</help> <completionHelp> <script>${vyos_completion_dir}/list_interfaces</script> </completionHelp> diff --git a/interface-definitions/include/monitoring/blackbox-exporter-module-commons.xml.i b/interface-definitions/include/monitoring/blackbox-exporter-module-commons.xml.i new file mode 100644 index 000000000..a97eb5232 --- /dev/null +++ b/interface-definitions/include/monitoring/blackbox-exporter-module-commons.xml.i @@ -0,0 +1,39 @@ +<!-- include start from monitoring/blackbox-module-commons.xml.i --> +<leafNode name="timeout"> + <properties> + <help>Timeout in seconds for the probe request</help> + <valueHelp> + <format>u32:1-60</format> + <description>Timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-60"/> + </constraint> + <constraintErrorMessage>Timeout must be between 1 and 60 seconds</constraintErrorMessage> + </properties> + <defaultValue>5</defaultValue> +</leafNode> +<leafNode name="preferred-ip-protocol"> + <properties> + <help>Preferred IP protocol for this module</help> + <valueHelp> + <format>ipv4</format> + <description>Prefer IPv4</description> + </valueHelp> + <valueHelp> + <format>ipv6</format> + <description>Prefer IPv6</description> + </valueHelp> + <constraint> + <regex>(ipv4|ipv6)</regex> + </constraint> + </properties> + <defaultValue>ip6</defaultValue> +</leafNode> +<leafNode name="ip-protocol-fallback"> + <properties> + <help>Allow fallback to other IP protocol if necessary</help> + <valueless/> + </properties> +</leafNode> +<!-- include end -->
\ No newline at end of file diff --git a/interface-definitions/include/static/static-route-interface.xml.i b/interface-definitions/include/static/static-route-interface.xml.i deleted file mode 100644 index cb5436847..000000000 --- a/interface-definitions/include/static/static-route-interface.xml.i +++ /dev/null @@ -1,17 +0,0 @@ -<!-- include start from static/static-route-interface.xml.i --> -<leafNode name="interface"> - <properties> - <help>Gateway interface name</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <valueHelp> - <format>txt</format> - <description>Gateway interface name</description> - </valueHelp> - <constraint> - #include <include/constraint/interface-name.xml.i> - </constraint> - </properties> -</leafNode> -<!-- include end --> diff --git a/interface-definitions/include/static/static-route.xml.i b/interface-definitions/include/static/static-route.xml.i index fd7366286..c261874f5 100644 --- a/interface-definitions/include/static/static-route.xml.i +++ b/interface-definitions/include/static/static-route.xml.i @@ -49,7 +49,7 @@ <children> #include <include/generic-disable-node.xml.i> #include <include/static/static-route-distance.xml.i> - #include <include/static/static-route-interface.xml.i> + #include <include/generic-interface.xml.i> #include <include/static/static-route-vrf.xml.i> <node name="bfd"> <properties> diff --git a/interface-definitions/include/static/static-route6.xml.i b/interface-definitions/include/static/static-route6.xml.i index 6fcc18b8a..a3d972d39 100644 --- a/interface-definitions/include/static/static-route6.xml.i +++ b/interface-definitions/include/static/static-route6.xml.i @@ -49,7 +49,7 @@ <children> #include <include/generic-disable-node.xml.i> #include <include/static/static-route-distance.xml.i> - #include <include/static/static-route-interface.xml.i> + #include <include/generic-interface.xml.i> #include <include/static/static-route-segments.xml.i> #include <include/static/static-route-vrf.xml.i> <node name="bfd"> diff --git a/interface-definitions/include/version/firewall-version.xml.i b/interface-definitions/include/version/firewall-version.xml.i index a15cf0eec..1a8098297 100644 --- a/interface-definitions/include/version/firewall-version.xml.i +++ b/interface-definitions/include/version/firewall-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/firewall-version.xml.i --> -<syntaxVersion component='firewall' version='17'></syntaxVersion> +<syntaxVersion component='firewall' version='18'></syntaxVersion> <!-- include end --> diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in index 89f990d41..b3559a626 100644 --- a/interface-definitions/interfaces_ethernet.xml.in +++ b/interface-definitions/interfaces_ethernet.xml.in @@ -56,6 +56,12 @@ </properties> <defaultValue>auto</defaultValue> </leafNode> + <leafNode name="switchdev"> + <properties> + <help>Enables switchdev mode on interface</help> + <valueless/> + </properties> + </leafNode> #include <include/interface/eapol.xml.i> <node name="evpn"> <properties> diff --git a/interface-definitions/pki.xml.in b/interface-definitions/pki.xml.in index 5c0b735ef..161f20b33 100644 --- a/interface-definitions/pki.xml.in +++ b/interface-definitions/pki.xml.in @@ -202,30 +202,6 @@ </node> </children> </tagNode> - <tagNode name="openssh"> - <properties> - <help>OpenSSH public and private keys</help> - </properties> - <children> - <node name="public"> - <properties> - <help>Public key</help> - </properties> - <children> - #include <include/pki/cli-public-key-base64.xml.i> - </children> - </node> - <node name="private"> - <properties> - <help>Private key</help> - </properties> - <children> - #include <include/pki/cli-private-key-base64.xml.i> - #include <include/pki/password-protected.xml.i> - </children> - </node> - </children> - </tagNode> <node name="openvpn"> <properties> <help>OpenVPN keys</help> diff --git a/interface-definitions/protocols_failover.xml.in b/interface-definitions/protocols_failover.xml.in index f70975949..fae9be76a 100644 --- a/interface-definitions/protocols_failover.xml.in +++ b/interface-definitions/protocols_failover.xml.in @@ -110,7 +110,7 @@ </leafNode> </children> </node> - #include <include/static/static-route-interface.xml.i> + #include <include/generic-interface.xml.i> <leafNode name="metric"> <properties> <help>Route metric for this gateway</help> diff --git a/interface-definitions/protocols_segment-routing.xml.in b/interface-definitions/protocols_segment-routing.xml.in index c299f624e..688b253b6 100644 --- a/interface-definitions/protocols_segment-routing.xml.in +++ b/interface-definitions/protocols_segment-routing.xml.in @@ -126,6 +126,25 @@ </properties> <defaultValue>24</defaultValue> </leafNode> + <leafNode name="format"> + <properties> + <help>SRv6 SID format</help> + <completionHelp> + <list>uncompressed-f4024 usid-f3216</list> + </completionHelp> + <valueHelp> + <format>uncompressed-f4024</format> + <description>Uncompressed f4024 format</description> + </valueHelp> + <valueHelp> + <format>usid-f3216</format> + <description>usid-f3216 format</description> + </valueHelp> + <constraint> + <regex>(uncompressed-f4024|usid-f3216)</regex> + </constraint> + </properties> + </leafNode> </children> </tagNode> </children> diff --git a/interface-definitions/service_dhcpv6-server.xml.in b/interface-definitions/service_dhcpv6-server.xml.in index cf14388e8..a6763a345 100644 --- a/interface-definitions/service_dhcpv6-server.xml.in +++ b/interface-definitions/service_dhcpv6-server.xml.in @@ -48,21 +48,7 @@ <children> #include <include/generic-disable-node.xml.i> #include <include/generic-description.xml.i> - <leafNode name="interface"> - <properties> - <help>Optional interface for this shared network to accept requests from</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> - </leafNode> + #include <include/generic-interface.xml.i> #include <include/dhcp/option-v6.xml.i> <tagNode name="subnet"> <properties> @@ -77,21 +63,7 @@ </properties> <children> #include <include/dhcp/option-v6.xml.i> - <leafNode name="interface"> - <properties> - <help>Optional interface for this subnet to accept requests from</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> - </leafNode> + #include <include/generic-interface.xml.i> <tagNode name="range"> <properties> <help>Parameters setting ranges for assigning IPv6 addresses</help> diff --git a/interface-definitions/service_monitoring_prometheus.xml.in b/interface-definitions/service_monitoring_prometheus.xml.in index 24f31e15c..8bcebf5f3 100644 --- a/interface-definitions/service_monitoring_prometheus.xml.in +++ b/interface-definitions/service_monitoring_prometheus.xml.in @@ -21,6 +21,19 @@ <defaultValue>9100</defaultValue> </leafNode> #include <include/interface/vrf.xml.i> + <node name="collectors"> + <properties> + <help>Collectors specific configuration</help> + </properties> + <children> + <leafNode name="textfile"> + <properties> + <help>Enables textfile collector to read from /run/node_exporter/collector</help> + <valueless/> + </properties> + </leafNode> + </children> + </node> </children> </node> <node name="frr-exporter"> @@ -36,6 +49,82 @@ #include <include/interface/vrf.xml.i> </children> </node> + <node name="blackbox-exporter"> + <properties> + <help>Prometheus exporter for probing endpoints</help> + </properties> + <children> + #include <include/listen-address.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>9115</defaultValue> + </leafNode> + #include <include/interface/vrf.xml.i> + <node name="modules"> + <properties> + <help>Configure blackbox exporter modules</help> + </properties> + <children> + <node name="dns"> + <properties> + <help>Configure dns module</help> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Name of the dns module</help> + </properties> + <children> + <leafNode name="query-name"> + <properties> + <help>Name to be queried</help> + <constraint> + <validator name="fqdn"/> + </constraint> + </properties> + </leafNode> + <leafNode name="query-type"> + <properties> + <help>DNS query type</help> + <valueHelp> + <format>ANY</format> + <description>Query any DNS record</description> + </valueHelp> + <valueHelp> + <format>A</format> + <description>Query IPv4 address record</description> + </valueHelp> + <valueHelp> + <format>AAAA</format> + <description>Query IPv6 address record</description> + </valueHelp> + </properties> + <defaultValue>ANY</defaultValue> + </leafNode> + #include <include/monitoring/blackbox-exporter-module-commons.xml.i> + </children> + </tagNode> + </children> + </node> + <node name="icmp"> + <properties> + <help>Configure icmp module</help> + </properties> + <children> + <tagNode name="name"> + <properties> + <help>Name of the icmp module</help> + </properties> + <children> + #include <include/monitoring/blackbox-exporter-module-commons.xml.i> + </children> + </tagNode> + </children> + </node> + </children> + </node> + </children> + </node> </children> </node> </children> diff --git a/interface-definitions/service_ndp-proxy.xml.in b/interface-definitions/service_ndp-proxy.xml.in index aabba3f4e..327ce89d5 100644 --- a/interface-definitions/service_ndp-proxy.xml.in +++ b/interface-definitions/service_ndp-proxy.xml.in @@ -111,17 +111,7 @@ </properties> <defaultValue>static</defaultValue> </leafNode> - <leafNode name="interface"> - <properties> - <help>Interface to forward Neighbor Solicitation message through. Required for "iface" mode</help> - <completionHelp> - <script>${vyos_completion_dir}/list_interfaces</script> - </completionHelp> - <constraint> - #include <include/constraint/interface-name.xml.i> - </constraint> - </properties> - </leafNode> + #include <include/generic-interface.xml.i> </children> </tagNode> </children> diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in index 0c99fd261..32215e9d2 100644 --- a/interface-definitions/service_pppoe-server.xml.in +++ b/interface-definitions/service_pppoe-server.xml.in @@ -63,6 +63,12 @@ </completionHelp> </properties> <children> + <leafNode name="combined"> + <properties> + <help>Listen on both VLANs and the base interface</help> + <valueless/> + </properties> + </leafNode> #include <include/accel-ppp/vlan.xml.i> #include <include/accel-ppp/vlan-mon.xml.i> </children> diff --git a/interface-definitions/service_ssh.xml.in b/interface-definitions/service_ssh.xml.in index 221e451d1..14d358c78 100644 --- a/interface-definitions/service_ssh.xml.in +++ b/interface-definitions/service_ssh.xml.in @@ -275,6 +275,14 @@ </constraint> </properties> </leafNode> + <node name="trusted-user-ca-key"> + <properties> + <help>Trusted user CA key</help> + </properties> + <children> + #include <include/pki/ca-certificate.xml.i> + </children> + </node> #include <include/vrf-multi.xml.i> </children> </node> diff --git a/op-mode-definitions/monitor-log.xml.in b/op-mode-definitions/monitor-log.xml.in index c9dc49b3a..b9ef8f48e 100644 --- a/op-mode-definitions/monitor-log.xml.in +++ b/op-mode-definitions/monitor-log.xml.in @@ -377,6 +377,12 @@ </properties> <command>journalctl --no-hostname --boot --follow --unit vyos-configd.service</command> </leafNode> + <leafNode name="vyos-domain-resolver"> + <properties> + <help>Monitor last lines of VyOS domain resolver daemon log</help> + </properties> + <command>journalctl --no-hostname --boot --follow --unit vyos-domain-resolver.service</command> + </leafNode> <node name="wireless"> <properties> <help>Monitor last lines of Wireless interface log</help> diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in index 9dcebb6af..7ace50cc9 100755 --- a/op-mode-definitions/show-log.xml.in +++ b/op-mode-definitions/show-log.xml.in @@ -905,6 +905,12 @@ </properties> <command>journalctl --no-hostname --boot --unit vyos-configd.service</command> </leafNode> + <leafNode name="vyos-domain-resolver"> + <properties> + <help>Show log for VyOS domain resolver daemon</help> + </properties> + <command>journalctl --no-hostname --boot --unit vyos-domain-resolver.service</command> + </leafNode> <node name="wireless"> <properties> <help>Show log for Wireless interface</help> diff --git a/python/vyos/frrender.py b/python/vyos/frrender.py index badc5d59f..544983b2c 100644 --- a/python/vyos/frrender.py +++ b/python/vyos/frrender.py @@ -218,11 +218,11 @@ def get_frrender_dict(conf, argv=None) -> dict: # values present on the CLI - that's why we have if conf.exists() eigrp_cli_path = ['protocols', 'eigrp'] if conf.exists(eigrp_cli_path): - isis = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'), - get_first_key=True, - no_tag_node_value_mangle=True, - with_recursive_defaults=True) - dict.update({'eigrp' : isis}) + eigrp = conf.get_config_dict(eigrp_cli_path, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True, + with_recursive_defaults=True) + dict.update({'eigrp' : eigrp}) elif conf.exists_effective(eigrp_cli_path): dict.update({'eigrp' : {'deleted' : ''}}) @@ -360,17 +360,24 @@ def get_frrender_dict(conf, argv=None) -> dict: static = conf.get_config_dict(static_cli_path, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) - - # T3680 - get a list of all interfaces currently configured to use DHCP - tmp = get_dhcp_interfaces(conf) - if tmp: static.update({'dhcp' : tmp}) - tmp = get_pppoe_interfaces(conf) - if tmp: static.update({'pppoe' : tmp}) - dict.update({'static' : static}) elif conf.exists_effective(static_cli_path): dict.update({'static' : {'deleted' : ''}}) + # T3680 - get a list of all interfaces currently configured to use DHCP + tmp = get_dhcp_interfaces(conf) + if tmp: + if 'static' in dict: + dict['static'].update({'dhcp' : tmp}) + else: + dict.update({'static' : {'dhcp' : tmp}}) + tmp = get_pppoe_interfaces(conf) + if tmp: + if 'static' in dict: + dict['static'].update({'pppoe' : tmp}) + else: + dict.update({'static' : {'pppoe' : tmp}}) + # keep a re-usable list of dependent VRFs dependent_vrfs_default = {} if 'bgp' in dict: @@ -534,17 +541,32 @@ def get_frrender_dict(conf, argv=None) -> dict: dict.update({'vrf' : vrf}) + if os.path.exists(frr_debug_enable): + import pprint + pprint.pprint(dict) + return dict class FRRender: + cached_config_dict = {} def __init__(self): self._frr_conf = '/run/frr/config/vyos.frr.conf' def generate(self, config_dict) -> None: + """ + Generate FRR configuration file + Returns False if no changes to configuration were made, otherwise True + """ if not isinstance(config_dict, dict): tmp = type(config_dict) raise ValueError(f'Config must be of type "dict" and not "{tmp}"!') + + if self.cached_config_dict == config_dict: + debug('FRR: NO CHANGES DETECTED') + return False + self.cached_config_dict = config_dict + def inline_helper(config_dict) -> str: output = '!\n' if 'babel' in config_dict and 'deleted' not in config_dict['babel']: @@ -639,7 +661,7 @@ class FRRender: debug(output) write_file(self._frr_conf, output) debug('FRR: RENDERING CONFIG COMPLETE') - return None + return True def apply(self, count_max=5): count = 0 @@ -649,7 +671,7 @@ class FRRender: debug(f'FRR: reloading configuration - tries: {count} | Python class ID: {id(self)}') cmdline = '/usr/lib/frr/frr-reload.py --reload' if os.path.exists(frr_debug_enable): - cmdline += ' --debug' + cmdline += ' --debug --stdout' rc, emsg = rc_cmd(f'{cmdline} {self._frr_conf}') if rc != 0: sleep(2) diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 50dd0f396..d0c03dbe0 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -26,11 +26,13 @@ from vyos.utils.file import read_file from vyos.utils.process import run from vyos.utils.assertion import assert_list + @Interface.register class EthernetIf(Interface): """ Abstraction of a Linux Ethernet Interface """ + iftype = 'ethernet' definition = { **Interface.definition, @@ -41,7 +43,7 @@ class EthernetIf(Interface): 'broadcast': True, 'bridgeable': True, 'eternal': '(lan|eth|eno|ens|enp|enx)[0-9]+$', - } + }, } @staticmethod @@ -49,32 +51,35 @@ class EthernetIf(Interface): run(f'ethtool --features {ifname} {option} {value}') return False - _command_set = {**Interface._command_set, **{ - 'gro': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v), - }, - 'gso': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), - }, - 'hw-tc-offload': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'hw-tc-offload', v), - }, - 'lro': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), - }, - 'sg': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), - }, - 'tso': { - 'validate': lambda v: assert_list(v, ['on', 'off']), - 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), + _command_set = { + **Interface._command_set, + **{ + 'gro': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'gro', v), + }, + 'gso': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'gso', v), + }, + 'hw-tc-offload': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'hw-tc-offload', v), + }, + 'lro': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'lro', v), + }, + 'sg': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'sg', v), + }, + 'tso': { + 'validate': lambda v: assert_list(v, ['on', 'off']), + 'possible': lambda i, v: EthernetIf.feature(i, 'tso', v), + }, }, - }} + } @staticmethod def get_bond_member_allowed_options() -> list: @@ -106,7 +111,7 @@ class EthernetIf(Interface): 'ring_buffer.rx', 'ring_buffer.tx', 'speed', - 'hw_id' + 'hw_id', ] return bond_allowed_sections @@ -130,7 +135,11 @@ class EthernetIf(Interface): self.set_admin_state('down') # Remove all VLAN subinterfaces - filter with the VLAN dot - for vlan in [x for x in Section.interfaces(self.iftype) if x.startswith(f'{self.ifname}.')]: + for vlan in [ + x + for x in Section.interfaces(self.iftype) + if x.startswith(f'{self.ifname}.') + ]: Interface(vlan).remove() super().remove() @@ -149,10 +158,12 @@ class EthernetIf(Interface): ifname = self.config['ifname'] if enable not in ['on', 'off']: - raise ValueError("Value out of range") + raise ValueError('Value out of range') if not self.ethtool.check_flow_control(): - self._debug_msg(f'NIC driver does not support changing flow control settings!') + self._debug_msg( + 'NIC driver does not support changing flow control settings!' + ) return False current = self.ethtool.get_flow_control() @@ -180,12 +191,24 @@ class EthernetIf(Interface): """ ifname = self.config['ifname'] - if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', - '25000', '40000', '50000', '100000', '400000']: - raise ValueError("Value out of range (speed)") + if speed not in [ + 'auto', + '10', + '100', + '1000', + '2500', + '5000', + '10000', + '25000', + '40000', + '50000', + '100000', + '400000', + ]: + raise ValueError('Value out of range (speed)') if duplex not in ['auto', 'full', 'half']: - raise ValueError("Value out of range (duplex)") + raise ValueError('Value out of range (duplex)') if not self.ethtool.check_speed_duplex(speed, duplex): Warning(f'changing speed/duplex setting on "{ifname}" is unsupported!') @@ -224,7 +247,9 @@ class EthernetIf(Interface): # but they do not actually support it either. # In that case it's probably better to ignore the error # than end up with a broken config. - print('Warning: could not set speed/duplex settings: operation not permitted!') + print( + 'Warning: could not set speed/duplex settings: operation not permitted!' + ) def set_gro(self, state): """ @@ -243,7 +268,9 @@ class EthernetIf(Interface): if not fixed: return self.set_interface('gro', 'on' if state else 'off') else: - print('Adapter does not support changing generic-receive-offload settings!') + print( + 'Adapter does not support changing generic-receive-offload settings!' + ) return False def set_gso(self, state): @@ -262,7 +289,9 @@ class EthernetIf(Interface): if not fixed: return self.set_interface('gso', 'on' if state else 'off') else: - print('Adapter does not support changing generic-segmentation-offload settings!') + print( + 'Adapter does not support changing generic-segmentation-offload settings!' + ) return False def set_hw_tc_offload(self, state): @@ -300,7 +329,9 @@ class EthernetIf(Interface): if not fixed: return self.set_interface('lro', 'on' if state else 'off') else: - print('Adapter does not support changing large-receive-offload settings!') + print( + 'Adapter does not support changing large-receive-offload settings!' + ) return False def set_rps(self, state): @@ -332,13 +363,15 @@ class EthernetIf(Interface): for i in range(0, cpu_count, 32): # Extract the next 32-bit chunk chunk = (rps_cpus >> i) & 0xFFFFFFFF - hex_chunks.append(f"{chunk:08x}") + hex_chunks.append(f'{chunk:08x}') # Join the chunks with commas - rps_cpus = ",".join(hex_chunks) + rps_cpus = ','.join(hex_chunks) for i in range(queues): - self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus) + self._write_sysfs( + f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_cpus', rps_cpus + ) # send bitmask representation as hex string without leading '0x' return True @@ -348,10 +381,13 @@ class EthernetIf(Interface): queues = len(glob(f'/sys/class/net/{self.ifname}/queues/rx-*')) if state: global_rfs_flow = 32768 - rfs_flow = int(global_rfs_flow/queues) + rfs_flow = int(global_rfs_flow / queues) for i in range(0, queues): - self._write_sysfs(f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', rfs_flow) + self._write_sysfs( + f'/sys/class/net/{self.ifname}/queues/rx-{i}/rps_flow_cnt', + rfs_flow, + ) return True @@ -392,7 +428,9 @@ class EthernetIf(Interface): if not fixed: return self.set_interface('tso', 'on' if state else 'off') else: - print('Adapter does not support changing tcp-segmentation-offload settings!') + print( + 'Adapter does not support changing tcp-segmentation-offload settings!' + ) return False def set_ring_buffer(self, rx_tx, size): @@ -417,39 +455,64 @@ class EthernetIf(Interface): print(f'could not set "{rx_tx}" ring-buffer for {ifname}') return output + def set_switchdev(self, enable): + ifname = self.config['ifname'] + addr, code = self._popen( + f"ethtool -i {ifname} | grep bus-info | awk '{{print $2}}'" + ) + if code != 0: + print(f'could not resolve PCIe address of {ifname}') + return + + enabled = False + state, code = self._popen( + f"/sbin/devlink dev eswitch show pci/{addr} | awk '{{print $3}}'" + ) + if code == 0 and state == 'switchdev': + enabled = True + + if enable and not enabled: + output, code = self._popen( + f'/sbin/devlink dev eswitch set pci/{addr} mode switchdev' + ) + if code != 0: + print(f'{ifname} does not support switchdev mode') + elif not enable and enabled: + self._cmd(f'/sbin/devlink dev eswitch set pci/{addr} mode legacy') + def update(self, config): - """ General helper function which works on a dictionary retrived by + """General helper function which works on a dictionary retrived by get_config_dict(). It's main intention is to consolidate the scattered interface setup code and provide a single point of entry when workin - on any interface. """ + on any interface.""" # disable ethernet flow control (pause frames) value = 'off' if 'disable_flow_control' in config else 'on' self.set_flow_control(value) # GRO (generic receive offload) - self.set_gro(dict_search('offload.gro', config) != None) + self.set_gro(dict_search('offload.gro', config) is not None) # GSO (generic segmentation offload) - self.set_gso(dict_search('offload.gso', config) != None) + self.set_gso(dict_search('offload.gso', config) is not None) # GSO (generic segmentation offload) - self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) != None) + self.set_hw_tc_offload(dict_search('offload.hw_tc_offload', config) is not None) # LRO (large receive offload) - self.set_lro(dict_search('offload.lro', config) != None) + self.set_lro(dict_search('offload.lro', config) is not None) # RPS - Receive Packet Steering - self.set_rps(dict_search('offload.rps', config) != None) + self.set_rps(dict_search('offload.rps', config) is not None) # RFS - Receive Flow Steering - self.set_rfs(dict_search('offload.rfs', config) != None) + self.set_rfs(dict_search('offload.rfs', config) is not None) # scatter-gather option - self.set_sg(dict_search('offload.sg', config) != None) + self.set_sg(dict_search('offload.sg', config) is not None) # TSO (TCP segmentation offloading) - self.set_tso(dict_search('offload.tso', config) != None) + self.set_tso(dict_search('offload.tso', config) is not None) # Set physical interface speed and duplex if 'speed_duplex_changed' in config: @@ -463,6 +526,8 @@ class EthernetIf(Interface): for rx_tx, size in config['ring_buffer'].items(): self.set_ring_buffer(rx_tx, size) + self.set_switchdev('switchdev' in config) + # call base class last super().update(config) diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index cad1685a9..de821ab60 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1423,11 +1423,13 @@ class Interface(Control): tmp = get_interface_address(self.ifname) if tmp and 'addr_info' in tmp: for address_dict in tmp['addr_info']: - # Only remove dynamic assigned addresses - if 'dynamic' not in address_dict: - continue - address = address_dict['local'] - self.del_addr(address) + if address_dict['family'] == 'inet': + # Only remove dynamic assigned addresses + if 'dynamic' not in address_dict: + continue + address = address_dict['local'] + prefixlen = address_dict['prefixlen'] + self.del_addr(f'{address}/{prefixlen}') # cleanup old config files for file in [dhclient_config_file, systemd_override_file, dhclient_lease_file]: diff --git a/python/vyos/utils/network.py b/python/vyos/utils/network.py index 8fce08de0..dc0c0a6d6 100644 --- a/python/vyos/utils/network.py +++ b/python/vyos/utils/network.py @@ -69,7 +69,9 @@ def get_vrf_members(vrf: str) -> list: answer = json.loads(output) for data in answer: if 'ifname' in data: - interfaces.append(data.get('ifname')) + # Skip PIM interfaces which appears in VRF + if 'pim' not in data.get('ifname'): + interfaces.append(data.get('ifname')) except: pass return interfaces diff --git a/smoketest/scripts/cli/base_interfaces_test.py b/smoketest/scripts/cli/base_interfaces_test.py index 593b4b415..c19bfcfe2 100644 --- a/smoketest/scripts/cli/base_interfaces_test.py +++ b/smoketest/scripts/cli/base_interfaces_test.py @@ -19,6 +19,7 @@ from netifaces import AF_INET6 from netifaces import ifaddresses from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.defaults import directories @@ -181,6 +182,9 @@ class BasicInterfaceTest: section = Section.section(span) cls.cli_set(cls, ['interfaces', section, span]) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + @classmethod def tearDownClass(cls): # Tear down mirror interfaces for SPAN (Switch Port Analyzer) diff --git a/smoketest/scripts/cli/base_vyostest_shim.py b/smoketest/scripts/cli/base_vyostest_shim.py index d95071d1a..a54622700 100644 --- a/smoketest/scripts/cli/base_vyostest_shim.py +++ b/smoketest/scripts/cli/base_vyostest_shim.py @@ -18,7 +18,6 @@ import paramiko import pprint from time import sleep -from time import time from typing import Type from vyos.configsession import ConfigSession @@ -30,6 +29,14 @@ from vyos.utils.process import run save_config = '/tmp/vyos-smoketest-save' +# The commit process is not finished until all pending files from +# VYATTA_CHANGES_ONLY_DIR are copied to VYATTA_ACTIVE_CONFIGURATION_DIR. This +# is done inside libvyatta-cfg1 and the FUSE UnionFS part. On large non- +# interactive commits FUSE UnionFS might not replicate the real state in time, +# leading to errors when querying the working and effective configuration. +# TO BE DELETED AFTER SWITCH TO IN MEMORY CONFIG +CSTORE_GUARD_TIME = 4 + # This class acts as shim between individual Smoketests developed for VyOS and # the Python UnitTest framework. Before every test is loaded, we dump the current # system configuration and reload it after the test - despite the test results. @@ -44,7 +51,9 @@ class VyOSUnitTestSHIM: # trigger the certain failure condition. # Use "self.debug = True" in derived classes setUp() method debug = False - commit_guard = time() + # Time to wait after a commit to ensure the CStore is up to date + # only required for testcases using FRR + _commit_guard_time = 0 @classmethod def setUpClass(cls): cls._session = ConfigSession(os.getpid()) @@ -85,11 +94,12 @@ class VyOSUnitTestSHIM: if self.debug: print('commit') self._session.commit() - # during a commit there is a process opening commit_lock, and run() returns 0 + # During a commit there is a process opening commit_lock, and run() + # returns 0 while run(f'sudo lsof -nP {commit_lock}') == 0: sleep(0.250) - # reset getFRRconfig() guard timer - self.commit_guard = time() + # Wait for CStore completion for fast non-interactive commits + sleep(self._commit_guard_time) def op_mode(self, path : list) -> None: """ @@ -104,20 +114,27 @@ class VyOSUnitTestSHIM: pprint.pprint(out) return out - def getFRRconfig(self, string=None, end='$', endsection='^!', daemon='', guard_time=10, empty_retry=0): - """ Retrieve current "running configuration" from FRR """ - # Sometimes FRR needs some time after reloading the configuration to - # appear in vtysh. This is a workaround addiung a 10 second guard timer - # between the last cli_commit() and the first read of FRR config via vtysh - while (time() - self.commit_guard) < guard_time: - sleep(0.250) # wait 250 milliseconds - command = f'vtysh -c "show run {daemon} no-header"' - if string: command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' + def getFRRconfig(self, string=None, end='$', endsection='^!', + substring=None, endsubsection=None, empty_retry=0): + """ + Retrieve current "running configuration" from FRR + + string: search for a specific start string in the configuration + end: end of the section to search for (line ending) + endsection: end of the configuration + substring: search section under the result found by string + endsubsection: end of the subsection (usually something with "exit") + """ + command = f'vtysh -c "show run no-header"' + if string: + command += f' | sed -n "/^{string}{end}/,/{endsection}/p"' + if substring and endsubsection: + command += f' | sed -n "/^{substring}/,/{endsubsection}/p"' out = cmd(command) if self.debug: print(f'\n\ncommand "{command}" returned:\n') pprint.pprint(out) - if empty_retry: + if empty_retry > 0: retry_count = 0 while not out and retry_count < empty_retry: if self.debug and retry_count % 10 == 0: diff --git a/smoketest/scripts/cli/test_firewall.py b/smoketest/scripts/cli/test_firewall.py index 6420afa38..10301831e 100755 --- a/smoketest/scripts/cli/test_firewall.py +++ b/smoketest/scripts/cli/test_firewall.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -906,7 +906,7 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): def test_zone_basic(self): self.cli_set(['firewall', 'ipv4', 'name', 'smoketest', 'default-action', 'drop']) self.cli_set(['firewall', 'ipv6', 'name', 'smoketestv6', 'default-action', 'drop']) - self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'interface', 'eth0']) + self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'member', 'interface', 'eth0']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'from', 'smoketest-local', 'firewall', 'name', 'smoketest']) self.cli_set(['firewall', 'zone', 'smoketest-eth0', 'intra-zone-filtering', 'firewall', 'ipv6-name', 'smoketestv6']) self.cli_set(['firewall', 'zone', 'smoketest-local', 'local-zone']) @@ -964,6 +964,98 @@ class TestFirewall(VyOSUnitTestSHIM.TestCase): self.verify_nftables(nftables_search, 'ip vyos_filter') self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') + def test_zone_with_vrf(self): + self.cli_set(['firewall', 'ipv4', 'name', 'ZONE1-to-LOCAL', 'default-action', 'accept']) + self.cli_set(['firewall', 'ipv4', 'name', 'ZONE2_to_ZONE1', 'default-action', 'continue']) + self.cli_set(['firewall', 'ipv6', 'name', 'LOCAL_to_ZONE2_v6', 'default-action', 'drop']) + self.cli_set(['firewall', 'zone', 'LOCAL', 'from', 'ZONE1', 'firewall', 'name', 'ZONE1-to-LOCAL']) + self.cli_set(['firewall', 'zone', 'LOCAL', 'local-zone']) + self.cli_set(['firewall', 'zone', 'ZONE1', 'from', 'ZONE2', 'firewall', 'name', 'ZONE2_to_ZONE1']) + self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth1']) + self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'interface', 'eth2']) + self.cli_set(['firewall', 'zone', 'ZONE1', 'member', 'vrf', 'VRF-1']) + self.cli_set(['firewall', 'zone', 'ZONE2', 'from', 'LOCAL', 'firewall', 'ipv6-name', 'LOCAL_to_ZONE2_v6']) + self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'interface', 'vtun66']) + self.cli_set(['firewall', 'zone', 'ZONE2', 'member', 'vrf', 'VRF-2']) + + self.cli_set(['vrf', 'name', 'VRF-1', 'table', '101']) + self.cli_set(['vrf', 'name', 'VRF-2', 'table', '102']) + self.cli_set(['interfaces', 'ethernet', 'eth0', 'vrf', 'VRF-1']) + self.cli_set(['interfaces', 'vti', 'vti1', 'vrf', 'VRF-2']) + + self.cli_commit() + + nftables_search = [ + ['chain NAME_ZONE1-to-LOCAL'], + ['counter', 'accept', 'comment "NAM-ZONE1-to-LOCAL default-action accept"'], + ['chain NAME_ZONE2_to_ZONE1'], + ['counter', 'continue', 'comment "NAM-ZONE2_to_ZONE1 default-action continue"'], + ['chain VYOS_ZONE_FORWARD'], + ['type filter hook forward priority filter + 1'], + ['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'], + ['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'], + ['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'], + ['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'], + ['chain VYOS_ZONE_LOCAL'], + ['type filter hook input priority filter + 1'], + ['counter packets', 'jump VZONE_LOCAL_IN'], + ['chain VYOS_ZONE_OUTPUT'], + ['type filter hook output priority filter + 1'], + ['counter packets', 'jump VZONE_LOCAL_OUT'], + ['chain VZONE_LOCAL_IN'], + ['iifname { "eth1", "eth2" }', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'], + ['iifname "VRF-1"', 'counter packets', 'jump NAME_ZONE1-to-LOCAL'], + ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'], + ['chain VZONE_LOCAL_OUT'], + ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'], + ['chain VZONE_ZONE1'], + ['iifname { "eth1", "eth2" }', 'counter packets', 'return'], + ['iifname "VRF-1"', 'counter packets', 'return'], + ['iifname "vtun66"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'], + ['iifname "vtun66"', 'counter packets', 'return'], + ['iifname "VRF-2"', 'counter packets', 'jump NAME_ZONE2_to_ZONE1'], + ['iifname "VRF-2"', 'counter packets', 'return'], + ['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'], + ['chain VZONE_ZONE2'], + ['iifname "vtun66"', 'counter packets', 'return'], + ['iifname "VRF-2"', 'counter packets', 'return'], + ['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"'] + ] + + nftables_search_v6 = [ + ['chain NAME6_LOCAL_to_ZONE2_v6'], + ['counter', 'drop', 'comment "NAM-LOCAL_to_ZONE2_v6 default-action drop"'], + ['chain VYOS_ZONE_FORWARD'], + ['type filter hook forward priority filter + 1'], + ['oifname { "eth1", "eth2" }', 'counter packets', 'jump VZONE_ZONE1'], + ['oifname "eth0"', 'counter packets', 'jump VZONE_ZONE1'], + ['oifname "vtun66"', 'counter packets', 'jump VZONE_ZONE2'], + ['oifname "vti1"', 'counter packets', 'jump VZONE_ZONE2'], + ['chain VYOS_ZONE_LOCAL'], + ['type filter hook input priority filter + 1'], + ['counter packets', 'jump VZONE_LOCAL_IN'], + ['chain VYOS_ZONE_OUTPUT'], + ['type filter hook output priority filter + 1'], + ['counter packets', 'jump VZONE_LOCAL_OUT'], + ['chain VZONE_LOCAL_IN'], + ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'], + ['chain VZONE_LOCAL_OUT'], + ['oifname "vtun66"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'], + ['oifname "vti1"', 'counter packets', 'jump NAME6_LOCAL_to_ZONE2_v6'], + ['counter packets', 'drop', 'comment "zone_LOCAL default-action drop"'], + ['chain VZONE_ZONE1'], + ['iifname { "eth1", "eth2" }', 'counter packets', 'return'], + ['iifname "VRF-1"', 'counter packets', 'return'], + ['counter packets', 'drop', 'comment "zone_ZONE1 default-action drop"'], + ['chain VZONE_ZONE2'], + ['iifname "vtun66"', 'counter packets', 'return'], + ['iifname "VRF-2"', 'counter packets', 'return'], + ['counter packets', 'drop', 'comment "zone_ZONE2 default-action drop"'] + ] + + self.verify_nftables(nftables_search, 'ip vyos_filter') + self.verify_nftables(nftables_search_v6, 'ip6 vyos_filter') + def test_flow_offload(self): self.cli_set(['interfaces', 'ethernet', 'eth0', 'vif', '10']) self.cli_set(['firewall', 'flowtable', 'smoketest', 'interface', 'eth0.10']) diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 735e4f3c5..1a72f9dc4 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -24,7 +24,6 @@ from vyos.ifconfig.interface import Interface from vyos.configsession import ConfigSessionError from vyos.utils.network import get_interface_config from vyos.utils.file import read_file -from vyos.frrender import mgmt_daemon class BondingInterfaceTest(BasicInterfaceTest.TestCase): @classmethod @@ -287,7 +286,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): id = '5' for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' evpn mh es-id {id}', frrconfig) self.assertIn(f' evpn mh es-df-pref {id}', frrconfig) @@ -304,7 +303,7 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): id = '5' for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' evpn mh es-sys-mac 00:12:34:56:78:0{id}', frrconfig) self.assertIn(f' evpn mh uplink', frrconfig) diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py index c02ca613b..2b421e942 100755 --- a/smoketest/scripts/cli/test_interfaces_ethernet.py +++ b/smoketest/scripts/cli/test_interfaces_ethernet.py @@ -27,11 +27,11 @@ from netifaces import ifaddresses from base_interfaces_test import BasicInterfaceTest from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section -from vyos.frrender import mgmt_daemon -from vyos.utils.process import cmd -from vyos.utils.process import popen from vyos.utils.file import read_file +from vyos.utils.network import is_intf_addr_assigned from vyos.utils.network import is_ipv6_link_local +from vyos.utils.process import cmd +from vyos.utils.process import popen class EthernetInterfaceTest(BasicInterfaceTest.TestCase): @classmethod @@ -78,14 +78,18 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): continue self.assertFalse(is_intf_addr_assigned(interface, addr['addr'])) # Ensure no VLAN interfaces are left behind - tmp = [x for x in Section.interfaces('ethernet') if x.startswith(f'{interface}.')] + tmp = [ + x + for x in Section.interfaces('ethernet') + if x.startswith(f'{interface}.') + ] self.assertListEqual(tmp, []) def test_offloading_rps(self): # enable RPS on all available CPUs, RPS works with a CPU bitmask, # where each bit represents a CPU (core/thread). The formula below # expands to rps_cpus = 255 for a 8 core system - rps_cpus = (1 << os.cpu_count()) -1 + rps_cpus = (1 << os.cpu_count()) - 1 # XXX: we should probably reserve one core when the system is under # high preasure so we can still have a core left for housekeeping. @@ -101,7 +105,7 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: cpus = read_file(f'/sys/class/net/{interface}/queues/rx-0/rps_cpus') # remove the nasty ',' separation on larger strings - cpus = cpus.replace(',','') + cpus = cpus.replace(',', '') cpus = int(cpus, 16) self.assertEqual(f'{cpus:x}', f'{rps_cpus:x}') @@ -117,12 +121,14 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) - rfs_flow = int(global_rfs_flow/queues) + rfs_flow = int(global_rfs_flow / queues) for i in range(0, queues): - tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') + tmp = read_file( + f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt' + ) self.assertEqual(int(tmp), rfs_flow) - tmp = read_file(f'/proc/sys/net/core/rps_sock_flow_entries') + tmp = read_file('/proc/sys/net/core/rps_sock_flow_entries') self.assertEqual(int(tmp), global_rfs_flow) # delete configuration of RFS and check all values returned to default "0" @@ -133,12 +139,13 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): for interface in self._interfaces: queues = len(glob(f'/sys/class/net/{interface}/queues/rx-*')) - rfs_flow = int(global_rfs_flow/queues) + rfs_flow = int(global_rfs_flow / queues) for i in range(0, queues): - tmp = read_file(f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt') + tmp = read_file( + f'/sys/class/net/{interface}/queues/rx-{i}/rps_flow_cnt' + ) self.assertEqual(int(tmp), 0) - def test_non_existing_interface(self): unknonw_interface = self._base_path + ['eth667'] self.cli_set(unknonw_interface) @@ -213,15 +220,24 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase): out = loads(out) self.assertFalse(out[0]['autonegotiate']) - def test_ethtool_evpn_uplink_tarcking(self): + def test_ethtool_evpn_uplink_tracking(self): for interface in self._interfaces: self.cli_set(self._base_path + [interface, 'evpn', 'uplink']) self.cli_commit() for interface in self._interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=mgmt_daemon) - self.assertIn(f' evpn mh uplink', frrconfig) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') + self.assertIn(' evpn mh uplink', frrconfig) + + def test_switchdev(self): + interface = self._interfaces[0] + self.cli_set(self._base_path + [interface, 'switchdev']) + + # check validate() - virtual interfaces do not support switchdev + # should print out warning that enabling failed + + self.cli_delete(self._base_path + [interface, 'switchdev']) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py index 7ea1b610e..9d4fc0845 100755 --- a/smoketest/scripts/cli/test_policy.py +++ b/smoketest/scripts/cli/test_policy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.utils.process import cmd @@ -24,6 +25,17 @@ from vyos.utils.process import cmd base_path = ['policy'] class TestPolicy(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + super(TestPolicy, cls).setUpClass() + + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + cls.cli_delete(cls, ['vrf']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + def tearDown(self): self.cli_delete(base_path) self.cli_commit() diff --git a/smoketest/scripts/cli/test_policy_local-route.py b/smoketest/scripts/cli/test_policy_local-route.py index 8d6ba40dc..a4239b8a1 100644 --- a/smoketest/scripts/cli/test_policy_local-route.py +++ b/smoketest/scripts/cli/test_policy_local-route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2024 VyOS maintainers and contributors +# Copyright (C) 2024-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME interface = 'eth0' mark = '100' @@ -32,6 +33,8 @@ class TestPolicyLocalRoute(VyOSUnitTestSHIM.TestCase): # Clear out current configuration to allow running this test on a live system cls.cli_delete(cls, ['policy', 'local-route']) cls.cli_delete(cls, ['policy', 'local-route6']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME cls.cli_set(cls, ['vrf', 'name', vrf_name, 'table', vrf_rt_id]) diff --git a/smoketest/scripts/cli/test_policy_route.py b/smoketest/scripts/cli/test_policy_route.py index 672865eb0..53761b7d6 100755 --- a/smoketest/scripts/cli/test_policy_route.py +++ b/smoketest/scripts/cli/test_policy_route.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME mark = '100' conn_mark = '555' @@ -36,6 +37,8 @@ class TestPolicyRoute(VyOSUnitTestSHIM.TestCase): # Clear out current configuration to allow running this test on a live system cls.cli_delete(cls, ['policy', 'route']) cls.cli_delete(cls, ['policy', 'route6']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME cls.cli_set(cls, ['interfaces', 'ethernet', interface, 'address', interface_ip]) cls.cli_set(cls, ['protocols', 'static', 'table', table_id, 'route', '0.0.0.0/0', 'interface', interface]) diff --git a/smoketest/scripts/cli/test_protocols_babel.py b/smoketest/scripts/cli/test_protocols_babel.py index 12f6ecf38..7ecf54600 100755 --- a/smoketest/scripts/cli/test_protocols_babel.py +++ b/smoketest/scripts/cli/test_protocols_babel.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.ifconfig import Section from vyos.frrender import babel_daemon @@ -38,6 +39,8 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): cls.cli_delete(cls, base_path) cls.cli_delete(cls, ['policy', 'prefix-list']) cls.cli_delete(cls, ['policy', 'prefix-list6']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): # always destroy the entire babel configuration to make the processes @@ -62,7 +65,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) + frrconfig = self.getFRRconfig('router babel', endsection='^exit') self.assertIn(f' babel diversity', frrconfig) self.assertIn(f' babel diversity-factor {diversity_factor}', frrconfig) self.assertIn(f' babel resend-delay {resend_delay}', frrconfig) @@ -81,7 +84,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon, empty_retry=5) + frrconfig = self.getFRRconfig('router babel', endsection='^exit', empty_retry=5) for protocol in ipv4_protos: self.assertIn(f' redistribute ipv4 {protocol}', frrconfig) for protocol in ipv6_protos: @@ -150,7 +153,7 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) + frrconfig = self.getFRRconfig('router babel', endsection='^exit') self.assertIn(f' distribute-list {access_list_in4} in', frrconfig) self.assertIn(f' distribute-list {access_list_out4} out', frrconfig) self.assertIn(f' ipv6 distribute-list {access_list_in6} in', frrconfig) @@ -198,11 +201,11 @@ class TestProtocolsBABEL(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig('router babel', endsection='^exit', daemon=babel_daemon) + frrconfig = self.getFRRconfig('router babel', endsection='^exit') for interface in self._interfaces: self.assertIn(f' network {interface}', frrconfig) - iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit', daemon=babel_daemon) + iface_config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' babel channel {channel}', iface_config) self.assertIn(f' babel enable-timestamps', iface_config) self.assertIn(f' babel update-interval {def_update_interval}', iface_config) diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py index 32e39e8f7..2205cd9de 100755 --- a/smoketest/scripts/cli/test_protocols_bfd.py +++ b/smoketest/scripts/cli/test_protocols_bfd.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -17,12 +17,13 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError from vyos.frrender import bfd_daemon from vyos.utils.process import process_named_running base_path = ['protocols', 'bfd'] -frr_endsection = '^ exit' dum_if = 'dum1001' vrf_name = 'red' @@ -87,6 +88,9 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same cls.daemon_pid = process_named_running(bfd_daemon) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) @@ -131,7 +135,7 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig('bfd', endsection='^exit', daemon=bfd_daemon) + frrconfig = self.getFRRconfig('bfd', endsection='^exit') for peer, peer_config in peers.items(): tmp = f'peer {peer}' if 'multihop' in peer_config: @@ -144,8 +148,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): tmp += f' vrf {peer_config["vrf"]}' self.assertIn(tmp, frrconfig) - peerconfig = self.getFRRconfig(f' peer {peer}', end='', endsection=frr_endsection, - daemon=bfd_daemon) + peerconfig = self.getFRRconfig('bfd', endsection='^exit', substring=f' peer {peer}', + endsubsection='^ exit') if 'echo_mode' in peer_config: self.assertIn(f'echo-mode', peerconfig) if 'intv_echo' in peer_config: @@ -207,7 +211,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): # Verify FRR bgpd configuration for profile, profile_config in profiles.items(): - config = self.getFRRconfig(f' profile {profile}', endsection=frr_endsection) + config = self.getFRRconfig('bfd', endsection='^exit', + substring=f' profile {profile}', endsubsection='^ exit',) if 'echo_mode' in profile_config: self.assertIn(f' echo-mode', config) if 'intv_echo' in profile_config: @@ -229,8 +234,8 @@ class TestProtocolsBFD(VyOSUnitTestSHIM.TestCase): self.assertNotIn(f'shutdown', config) for peer, peer_config in peers.items(): - peerconfig = self.getFRRconfig(f' peer {peer}', end='', - endsection=frr_endsection, daemon=bfd_daemon) + peerconfig = self.getFRRconfig('bfd', endsection='^exit', + substring=f' peer {peer}', endsubsection='^ exit') if 'profile' in peer_config: self.assertIn(f' profile {peer_config["profile"]}', peerconfig) diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py index cdf18a051..761eb8bfe 100755 --- a/smoketest/scripts/cli/test_protocols_bgp.py +++ b/smoketest/scripts/cli/test_protocols_bgp.py @@ -19,6 +19,7 @@ import unittest from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.ifconfig import Section from vyos.configsession import ConfigSessionError @@ -200,6 +201,9 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'action', 'deny']) cls.cli_set(cls, ['policy', 'prefix-list6', prefix_list_out6, 'rule', '10', 'prefix', '2001:db8:2000::/64']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + @classmethod def tearDownClass(cls): cls.cli_delete(cls, ['policy', 'route-map']) @@ -217,7 +221,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router bgp', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig('router bgp', endsection='^exit') self.assertNotIn(f'router bgp', frrconfig) # check process health and continuity @@ -372,7 +376,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp allow-martian-nexthop', frrconfig) @@ -398,18 +402,21 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertNotIn(f'bgp ebgp-requires-policy', frrconfig) self.assertIn(f' no bgp suppress-duplicates', frrconfig) - afiv4_config = self.getFRRconfig(' address-family ipv4 unicast', - endsection='^ exit-address-family', daemon=bgp_daemon) + afiv4_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv4 unicast', + endsubsection='^ exit-address-family') self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) - afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast', - endsection='^ exit-address-family', daemon=bgp_daemon) + afiv4_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv4 labeled-unicast', + endsubsection='^ exit-address-family') self.assertIn(f' maximum-paths {max_path_v4}', afiv4_config) self.assertIn(f' maximum-paths ibgp {max_path_v4ibgp}', afiv4_config) - afiv6_config = self.getFRRconfig(' address-family ipv6 unicast', - endsection='^ exit-address-family', daemon=bgp_daemon) + afiv6_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv6 unicast', + endsubsection='^ exit-address-family') self.assertIn(f' maximum-paths {max_path_v6}', afiv6_config) self.assertIn(f' maximum-paths ibgp {max_path_v6ibgp}', afiv6_config) @@ -516,7 +523,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in neighbor_config.items(): @@ -621,7 +628,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) for peer, peer_config in peer_group_config.items(): @@ -670,7 +677,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv4 unicast', frrconfig) @@ -716,7 +723,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) # T2100: By default ebgp-requires-policy is disabled to keep VyOS @@ -758,7 +765,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) self.assertIn(f' neighbor {peer_group} remote-as {ASN}', frrconfig) @@ -793,7 +800,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', daemon=bgp_daemon) + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family l2vpn evpn', frrconfig) self.assertIn(f' advertise-all-vni', frrconfig) @@ -806,7 +813,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' flooding disable', frrconfig) self.assertIn(f' mac-vrf soo {soo}', frrconfig) for vni in vnis: - vniconfig = self.getFRRconfig(f' vni {vni}', endsection='^exit-vni') + vniconfig = self.getFRRconfig(f' vni {vni}', endsection='^ exit-vni') self.assertIn(f'vni {vni}', vniconfig) self.assertIn(f' advertise-default-gw', vniconfig) self.assertIn(f' advertise-svi-ip', vniconfig) @@ -849,7 +856,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR distances configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) for family in verify_families: self.assertIn(f'address-family {family}', frrconfig) @@ -887,7 +894,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) @@ -895,7 +902,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.assertIn(f' import vrf {vrf}', frrconfig) # Verify FRR bgpd configuration - frr_vrf_config = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}') + frr_vrf_config = self.getFRRconfig(f'router bgp {ASN} vrf {vrf}', endsection='^exit') self.assertIn(f'router bgp {ASN} vrf {vrf}', frr_vrf_config) self.assertIn(f' bgp router-id {router_id}', frr_vrf_config) @@ -913,7 +920,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' bgp router-id {router_id}', frrconfig) self.assertIn(f' bgp confederation identifier {confed_id}', frrconfig) @@ -930,7 +937,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {interface} interface v6only remote-as {remote_asn}', frrconfig) self.assertIn(f' address-family ipv6 unicast', frrconfig) @@ -962,11 +969,13 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR bgpd configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) for afi in ['ipv4', 'ipv6']: - afi_config = self.getFRRconfig(f' address-family {afi} unicast', endsection='exit-address-family', daemon=bgp_daemon) + afi_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=f' address-family {afi} unicast', + endsubsection='^ exit-address-family') self.assertIn(f'address-family {afi} unicast', afi_config) self.assertIn(f' export vpn', afi_config) self.assertIn(f' import vpn', afi_config) @@ -1011,7 +1020,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {neighbor} peer-group {peer_group}', frrconfig) self.assertIn(f' neighbor {peer_group} peer-group', frrconfig) @@ -1036,7 +1045,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {neighbor} remote-as {remote_asn}', frrconfig) self.assertIn(f' neighbor {neighbor} local-as {local_asn}', frrconfig) @@ -1061,8 +1070,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): base_path + ['address-family', import_afi, 'import', 'vrf', import_vrf]) self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') - frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f'address-family ipv4 unicast', frrconfig) @@ -1084,8 +1093,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): base_path + ['address-family', import_afi, 'import', 'vrf', import_vrf]) self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') - frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f'address-family ipv4 unicast', frrconfig) self.assertIn(f' import vrf {import_vrf}', frrconfig) @@ -1098,8 +1107,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): # Verify deleting existent vrf default if other vrfs were created self.create_bgp_instances_for_import_test() self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') - frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) self.cli_delete(base_path) @@ -1115,8 +1124,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): 'vpn', 'export', import_rd]) self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') - frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') + frrconfig_vrf = self.getFRRconfig(f'router bgp {ASN} vrf {import_vrf}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f'router bgp {ASN} vrf {import_vrf}', frrconfig_vrf) self.assertIn(f'address-family ipv4 unicast', frrconfig_vrf) @@ -1145,7 +1154,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}') + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' mpls bgp forwarding', frrconfig) @@ -1159,7 +1168,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}') + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' mpls bgp forwarding', frrconfig) self.cli_delete(['interfaces', 'ethernet', interface, 'vrf']) @@ -1179,7 +1188,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path + ['address-family', 'ipv4-unicast', 'sid']) self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' segment-routing srv6', frrconfig) self.assertIn(f' locator {locator_name}', frrconfig) @@ -1194,17 +1203,22 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' segment-routing srv6', frrconfig) self.assertIn(f' locator {locator_name}', frrconfig) - afiv4_config = self.getFRRconfig(' address-family ipv4 unicast') + afiv4_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv4 unicast', + endsubsection='^ exit-address-family') self.assertIn(f' sid vpn export {sid}', afiv4_config) self.assertIn(f' nexthop vpn export {nexthop_ipv4}', afiv4_config) - afiv6_config = self.getFRRconfig(' address-family ipv6 unicast') + + afiv6_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv6 unicast', + endsubsection='^ exit-address-family') self.assertIn(f' sid vpn export {sid}', afiv6_config) - self.assertIn(f' nexthop vpn export {nexthop_ipv6}', afiv4_config) + self.assertIn(f' nexthop vpn export {nexthop_ipv6}', afiv6_config) def test_bgp_25_ipv4_labeled_unicast_peer_group(self): pg_ipv4 = 'foo4' @@ -1218,14 +1232,16 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {pg_ipv4} peer-group', frrconfig) self.assertIn(f' neighbor {pg_ipv4} remote-as external', frrconfig) self.assertIn(f' bgp listen range {ipv4_prefix} peer-group {pg_ipv4}', frrconfig) self.assertIn(f' bgp labeled-unicast ipv4-explicit-null', frrconfig) - afiv4_config = self.getFRRconfig(' address-family ipv4 labeled-unicast') + afiv4_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv4 labeled-unicast', + endsubsection='^ exit-address-family') self.assertIn(f' neighbor {pg_ipv4} activate', afiv4_config) self.assertIn(f' neighbor {pg_ipv4} maximum-prefix {ipv4_max_prefix}', afiv4_config) @@ -1242,14 +1258,16 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'router bgp {ASN}', frrconfig) self.assertIn(f' neighbor {pg_ipv6} peer-group', frrconfig) self.assertIn(f' neighbor {pg_ipv6} remote-as external', frrconfig) self.assertIn(f' bgp listen range {ipv6_prefix} peer-group {pg_ipv6}', frrconfig) self.assertIn(f' bgp labeled-unicast ipv6-explicit-null', frrconfig) - afiv6_config = self.getFRRconfig(' address-family ipv6 labeled-unicast') + afiv6_config = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family ipv6 labeled-unicast', + endsubsection='^ exit-address-family') self.assertIn(f' neighbor {pg_ipv6} activate', afiv6_config) self.assertIn(f' neighbor {pg_ipv6} maximum-prefix {ipv6_max_prefix}', afiv6_config) @@ -1261,7 +1279,8 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['peer-group', 'peer1', 'remote-as', 'internal']) self.cli_commit() - conf = self.getFRRconfig(' address-family l2vpn evpn') + conf = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit', + substring=' address-family l2vpn evpn', endsubsection='^ exit-address-family') self.assertIn('neighbor peer1 route-reflector-client', conf) @@ -1300,7 +1319,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', int_neighbors[1], 'remote-as', ASN]) self.cli_commit() - conf = self.getFRRconfig(f'router bgp {ASN}') + conf = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') _common_config_check(conf) # test add internal remote-as to external group @@ -1315,7 +1334,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', f'{int(ASN) + 2}']) self.cli_commit() - conf = self.getFRRconfig(f'router bgp {ASN}') + conf = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') _common_config_check(conf) self.assertIn(f'neighbor {ext_neighbors[1]} remote-as {int(ASN) + 2}', conf) self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf) @@ -1327,7 +1346,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['neighbor', ext_neighbors[1], 'remote-as', 'external']) self.cli_commit() - conf = self.getFRRconfig(f'router bgp {ASN}') + conf = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') _common_config_check(conf, include_ras=False) self.assertIn(f'neighbor {int_neighbors[0]} remote-as internal', conf) @@ -1352,7 +1371,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() - conf = self.getFRRconfig(f'router bgp {ASN}') + conf = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'neighbor OVERLAY remote-as {int(ASN) + 1}', conf) self.assertIn(f'neighbor OVERLAY local-as {int(ASN) + 1}', conf) @@ -1405,7 +1424,7 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify bgpd bmp configuration - frrconfig = self.getFRRconfig(f'router bgp {ASN}') + frrconfig = self.getFRRconfig(f'router bgp {ASN}', endsection='^exit') self.assertIn(f'bmp mirror buffer-limit {mirror_buffer}', frrconfig) self.assertIn(f'bmp targets {target_name}', frrconfig) self.assertIn(f'bmp mirror', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py index bde32a1ca..598250d28 100755 --- a/smoketest/scripts/cli/test_protocols_isis.py +++ b/smoketest/scripts/cli/test_protocols_isis.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,13 +17,14 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.utils.process import process_named_running from vyos.frrender import isis_daemon base_path = ['protocols', 'isis'] - domain = 'VyOS' net = '49.0001.1921.6800.1002.00' @@ -39,6 +40,8 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): # out the current configuration :) cls.cli_delete(cls, base_path) cls.cli_delete(cls, ['vrf']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): # cleanup any possible VRF mess @@ -51,11 +54,6 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): # check process health and continuity self.assertEqual(self.daemon_pid, process_named_running(isis_daemon)) - def isis_base_config(self): - self.cli_set(base_path + ['net', net]) - for interface in self._interfaces: - self.cli_set(base_path + ['interface', interface]) - def test_isis_01_redistribute(self): prefix_list = 'EXPORT-ISIS' route_map = 'EXPORT-ISIS' @@ -73,7 +71,9 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError): self.cli_commit() - self.isis_base_config() + self.cli_set(base_path + ['net', net]) + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface]) self.cli_set(base_path + ['redistribute', 'ipv4', 'connected']) # verify() - Redistribute level-1 or level-2 should be specified @@ -88,14 +88,14 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' metric-style {metric_style}', tmp) self.assertIn(f' log-adjacency-changes', tmp) self.assertIn(f' redistribute ipv4 connected level-2 route-map {route_map}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -124,11 +124,11 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR isisd configuration - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f'router isis {domain}', tmp) self.assertIn(f' net {net}', tmp) - tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain} vrf {vrf}', endsection='^exit') self.assertIn(f'router isis {domain} vrf {vrf}', tmp) self.assertIn(f' net {net}', tmp) self.assertIn(f' advertise-high-metrics', tmp) @@ -141,7 +141,10 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): metric = '50' route_map = 'default-foo-' - self.isis_base_config() + self.cli_set(base_path + ['net', net]) + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface]) + for afi in ['ipv4', 'ipv6']: for level in ['level-1', 'level-2']: self.cli_set(base_path + ['default-information', 'originate', afi, level, 'always']) @@ -152,7 +155,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) for afi in ['ipv4', 'ipv6']: @@ -160,11 +163,10 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): route_map_name = route_map + level + afi self.assertIn(f' default-information originate {afi} {level} always route-map {route_map_name} metric {metric}', tmp) - def test_isis_05_password(self): password = 'foo' - self.isis_base_config() + self.cli_set(base_path + ['net', net]) for interface in self._interfaces: self.cli_set(base_path + ['interface', interface, 'password', 'plaintext-password', f'{password}-{interface}']) @@ -187,13 +189,13 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' domain-password clear {password}', tmp) self.assertIn(f' area-password clear {password}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' isis password clear {password}-{interface}', tmp) def test_isis_06_spf_delay_bfd(self): @@ -235,12 +237,12 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' spf-delay-ietf init-delay {init_delay} short-delay {short_delay} long-delay {long_delay} holddown {holddown} time-to-learn {time_to_learn}', tmp) for interface in self._interfaces: - tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) self.assertIn(f' isis network {network}', tmp) @@ -252,7 +254,6 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): global_block_high = "399" local_block_low = "400" local_block_high = "499" - interface = 'lo' maximum_stack_size = '5' prefix_one = '192.168.0.1/32' prefix_two = '192.168.0.2/32' @@ -264,7 +265,9 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): prefix_four_value = '65000' self.cli_set(base_path + ['net', net]) - self.cli_set(base_path + ['interface', interface]) + for interface in self._interfaces: + self.cli_set(base_path + ['interface', interface]) + self.cli_set(base_path + ['segment-routing', 'maximum-label-depth', maximum_stack_size]) self.cli_set(base_path + ['segment-routing', 'global-block', 'low-label-value', global_block_low]) self.cli_set(base_path + ['segment-routing', 'global-block', 'high-label-value', global_block_high]) @@ -283,7 +286,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' segment-routing on', tmp) self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', tmp) @@ -305,7 +308,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify main ISIS changes - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' mpls ldp-sync', tmp) self.assertIn(f' mpls ldp-sync holddown {holddown}', tmp) @@ -318,7 +321,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for interface in self._interfaces: # Verify interface changes for holddown - tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', tmp) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -332,7 +335,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for interface in self._interfaces: # Verify interface changes for disable - tmp = self.getFRRconfig(f'interface {interface}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', tmp) self.assertIn(f' ip router isis {domain}', tmp) self.assertIn(f' ipv6 router isis {domain}', tmp) @@ -355,7 +358,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'remote', 'prefix-list', prefix_list, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute remote-lfa prefix-list {prefix_list} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -365,7 +368,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'load-sharing', 'disable', level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute load-sharing disable {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -376,7 +379,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'priority-limit', priority, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute priority-limit {priority} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -388,7 +391,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for level in ['level-1', 'level-2']: self.cli_set(base_path + ['fast-reroute', 'lfa', 'local', 'tiebreaker', tiebreaker, 'index', index, level]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' fast-reroute lfa tiebreaker {tiebreaker} index {index} {level}', tmp) self.cli_delete(base_path + ['fast-reroute']) @@ -408,7 +411,7 @@ class TestProtocolsISIS(VyOSUnitTestSHIM.TestCase): for topology in topologies: self.cli_set(base_path + ['topology', topology]) self.cli_commit() - tmp = self.getFRRconfig(f'router isis {domain}', daemon=isis_daemon) + tmp = self.getFRRconfig(f'router isis {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' topology {topology}', tmp) diff --git a/smoketest/scripts/cli/test_protocols_mpls.py b/smoketest/scripts/cli/test_protocols_mpls.py index 88528973d..654f2f099 100755 --- a/smoketest/scripts/cli/test_protocols_mpls.py +++ b/smoketest/scripts/cli/test_protocols_mpls.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-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 @@ -17,6 +17,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.frrender import ldpd_daemon @@ -72,10 +74,11 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): # Retrieve FRR daemon PID - it is not allowed to crash, thus PID must remain the same cls.daemon_pid = process_named_running(ldpd_daemon) - # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): self.cli_delete(base_path) @@ -106,12 +109,14 @@ class TestProtocolsMPLS(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Validate configuration - frrconfig = self.getFRRconfig('mpls ldp', daemon=ldpd_daemon) + frrconfig = self.getFRRconfig('mpls ldp', endsection='^exit') self.assertIn(f'mpls ldp', frrconfig) self.assertIn(f' router-id {router_id}', frrconfig) # Validate AFI IPv4 - afiv4_config = self.getFRRconfig(' address-family ipv4', daemon=ldpd_daemon) + afiv4_config = self.getFRRconfig('mpls ldp', endsection='^exit', + substring=' address-family ipv4', + endsubsection='^ exit-address-family') self.assertIn(f' discovery transport-address {transport_ipv4_addr}', afiv4_config) for interface in interfaces: self.assertIn(f' interface {interface}', afiv4_config) diff --git a/smoketest/scripts/cli/test_protocols_openfabric.py b/smoketest/scripts/cli/test_protocols_openfabric.py index f1372d7ab..323b6cd74 100644 --- a/smoketest/scripts/cli/test_protocols_openfabric.py +++ b/smoketest/scripts/cli/test_protocols_openfabric.py @@ -17,6 +17,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError from vyos.utils.process import process_named_running from vyos.frrender import openfabric_daemon @@ -40,6 +42,8 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): self.cli_delete(base_path) @@ -75,14 +79,14 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'router openfabric {domain}', endsection='^exit') 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=openfabric_daemon) + tmp = self.getFRRconfig(f'interface {dummy_if}', endsection='^exit') self.assertIn(f' ip router openfabric {domain}', tmp) self.assertIn(f' ipv6 router openfabric {domain}', tmp) @@ -101,12 +105,12 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR openfabric configuration - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'router openfabric {domain}', endsection='^exit') self.assertIn(f'router openfabric {domain}', tmp) self.assertIn(f' net {net}', tmp) # Verify interface configuration - tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ip router openfabric {domain}', tmp) # for lo interface 'openfabric passive' is implied self.assertIn(f' openfabric passive', tmp) @@ -137,11 +141,11 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'router openfabric {domain}', endsection='^exit') self.assertIn(f' net {net}', tmp) self.assertIn(f' domain-password clear {password}', tmp) - tmp = self.getFRRconfig(f'interface {dummy_if}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'interface {dummy_if}', endsection='^exit') self.assertIn(f' openfabric password clear {password}-{dummy_if}', tmp) def test_openfabric_multiple_domains(self): @@ -165,22 +169,21 @@ class TestProtocolsOpenFabric(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR openfabric configuration - tmp = self.getFRRconfig(f'router openfabric {domain}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'router openfabric {domain}', endsection='^exit') self.assertIn(f'router openfabric {domain}', tmp) self.assertIn(f' net {net}', tmp) - tmp = self.getFRRconfig(f'router openfabric {domain_2}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'router openfabric {domain_2}', endsection='^exit') 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=openfabric_daemon) + tmp = self.getFRRconfig(f'interface {dummy_if}', endsection='^exit') self.assertIn(f' ip router openfabric {domain}', tmp) self.assertIn(f' ipv6 router openfabric {domain}', tmp) - tmp = self.getFRRconfig(f'interface {interface}', daemon=openfabric_daemon) + tmp = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ip router openfabric {domain_2}', tmp) - if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py index 599bf3930..77882737f 100755 --- a/smoketest/scripts/cli/test_protocols_ospf.py +++ b/smoketest/scripts/cli/test_protocols_ospf.py @@ -18,6 +18,7 @@ import unittest from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section @@ -45,6 +46,8 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME @classmethod def tearDownClass(cls): @@ -56,7 +59,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertNotIn(f'router ospf', frrconfig) # check process health and continuity @@ -68,7 +71,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults @@ -96,7 +99,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' compatible rfc1583', frrconfig) self.assertIn(f' auto-cost reference-bandwidth {bandwidth}', frrconfig) @@ -112,7 +115,6 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.assertNotIn(f' area 10 range 10.0.1.0/24 not-advertise', frrconfig) self.assertIn(f' area 10 range 10.0.2.0/24 not-advertise', frrconfig) - def test_ospf_03_access_list(self): acl = '100' seq = '10' @@ -128,14 +130,13 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults for ptotocol in protocols: self.assertIn(f' distribute-list {acl} out {ptotocol}', frrconfig) # defaults self.cli_delete(['policy', 'access-list', acl]) - def test_ospf_04_default_originate(self): seq = '100' metric = '50' @@ -149,7 +150,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -159,10 +160,9 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) - def test_ospf_05_options(self): global_distance = '128' intra_area = '100' @@ -201,7 +201,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' mpls-te on', frrconfig) self.assertIn(f' mpls-te router-address 0.0.0.0', frrconfig) # default @@ -224,7 +224,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['distance', 'ospf', 'inter-area', inter_area]) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f' distance ospf intra-area {intra_area} inter-area {inter_area} external {external}', frrconfig) # https://github.com/FRRouting/frr/issues/17011 @@ -247,7 +247,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) for neighbor in neighbors: self.assertIn(f' neighbor {neighbor} priority {priority} poll-interval {poll_interval}', frrconfig) # default @@ -266,7 +266,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -294,7 +294,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' area {area} shortcut {shortcut}', frrconfig) self.assertIn(f' area {area} virtual-link {virtual_link} hello-interval {hello} retransmit-interval {retransmit} retransmit-window {window_default} transmit-delay {transmit} dead-interval {dead}', frrconfig) @@ -326,13 +326,13 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' passive-interface default', frrconfig) for interface in interfaces: # Can not use daemon for getFRRconfig() as bandwidth parameter belongs to zebra process - config = self.getFRRconfig(f'interface {interface}') + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf authentication-key {password}', config) self.assertIn(f' ip ospf bfd', config) @@ -350,7 +350,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): for interface in interfaces: # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertNotIn(f'interface {interface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ip ospf', frrconfig) @@ -371,11 +371,11 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf area {area}', config) @@ -398,17 +398,17 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', daemon=ospf_daemon) + frrconfig = self.getFRRconfig(f'router ospf vrf {vrf}', endsection='^exit') self.assertIn(f'router ospf vrf {vrf}', frrconfig) self.assertIn(f' auto-cost reference-bandwidth 100', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # defaults - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', endsection='^exit') self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ip ospf area {area}', frrconfig) @@ -418,7 +418,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf_daemon) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', endsection='^exit') self.assertNotIn(f'interface {vrf_iface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ip ospf', frrconfig) @@ -444,7 +444,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) # default self.assertIn(f' network {network} area {area}', frrconfig) @@ -477,7 +477,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify all changes - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f' segment-routing on', frrconfig) self.assertIn(f' segment-routing global-block {global_block_low} {global_block_high} local-block {local_block_low} {local_block_high}', frrconfig) self.assertIn(f' segment-routing node-msd {maximum_stack_size}', frrconfig) @@ -495,7 +495,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify main OSPF changes - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' timers throttle spf 200 1000 10000', frrconfig) self.assertIn(f' mpls ldp-sync holddown {holddown}', frrconfig) @@ -508,7 +508,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): for interface in interfaces: # Verify interface changes for holddown - config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf dead-interval 40', config) self.assertIn(f' ip ospf mpls ldp-sync', config) @@ -522,7 +522,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): for interface in interfaces: # Verify interface changes for disable - config = self.getFRRconfig(f'interface {interface}', daemon=ospf_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ip ospf dead-interval 40', config) self.assertNotIn(f' ip ospf mpls ldp-sync', config) @@ -544,7 +544,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit') self.assertIn(f'router ospf', frrconfig) self.assertIn(f' capability opaque', frrconfig) self.assertIn(f' graceful-restart grace-period {period}', frrconfig) @@ -570,7 +570,7 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf', endsection='^exit', daemon=ospf_daemon, empty_retry=60) + frrconfig = self.getFRRconfig('router ospf', endsection='^exit', empty_retry=60) self.assertIn(f'router ospf', frrconfig) self.assertIn(f' network {network} area {area1}', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py index d961a4fdc..5da4c7c98 100755 --- a/smoketest/scripts/cli/test_protocols_ospfv3.py +++ b/smoketest/scripts/cli/test_protocols_ospfv3.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section @@ -44,6 +45,8 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME @classmethod def tearDownClass(cls): @@ -54,7 +57,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertNotIn(f'router ospf6', frrconfig) # check process health and continuity @@ -81,7 +84,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {default_area} range {prefix}', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) @@ -89,7 +92,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.assertIn(f' area {default_area} export-list {acl_name}', frrconfig) for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) + if_config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'ipv6 ospf6 area {default_area}', if_config) self.cli_delete(['policy', 'access-list6', acl_name]) @@ -110,7 +113,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' distance {dist_global}', frrconfig) self.assertIn(f' distance ospf6 intra-area {dist_intra_area} inter-area {dist_inter_area} external {dist_external}', frrconfig) @@ -134,7 +137,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) for protocol in redistribute: self.assertIn(f' redistribute {protocol} metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -165,13 +168,13 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) cost = '100' priority = '10' for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) + if_config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', if_config) self.assertIn(f' ipv6 ospf6 bfd', if_config) self.assertIn(f' ipv6 ospf6 bfd profile {bfd_profile}', if_config) @@ -188,7 +191,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - if_config = self.getFRRconfig(f'interface {interface}', daemon=ospf6_daemon) + if_config = self.getFRRconfig(f'interface {interface}', endsection='^exit') # There should be no OSPF6 configuration at all after interface removal self.assertNotIn(f' ipv6 ospf6', if_config) @@ -204,7 +207,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_stub} stub', frrconfig) self.assertIn(f' area {area_stub_nosum} stub no-summary', frrconfig) @@ -230,7 +233,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' area {area_nssa} nssa', frrconfig) self.assertIn(f' area {area_nssa_nosum} nssa default-information-originate no-summary', frrconfig) @@ -250,7 +253,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' default-information originate metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -259,7 +262,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f' default-information originate always metric {metric} metric-type {metric_type} route-map {route_map}', frrconfig) @@ -285,15 +288,15 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' ospf6 router-id {router_id}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', endsection='^exit') self.assertIn(f'interface {vrf_iface}', frrconfig) self.assertIn(f' ipv6 ospf6 bfd', frrconfig) - frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig(f'router ospf6 vrf {vrf}', endsection='^exit') self.assertIn(f'router ospf6 vrf {vrf}', frrconfig) self.assertIn(f' ospf6 router-id {router_id_vrf}', frrconfig) @@ -303,7 +306,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # T5467: It must also be removed from FRR config - frrconfig = self.getFRRconfig(f'interface {vrf_iface}', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig(f'interface {vrf_iface}', endsection='^exit') self.assertNotIn(f'interface {vrf_iface}', frrconfig) # There should be no OSPF related command at all under the interface self.assertNotIn(f' ipv6 ospf6', frrconfig) @@ -329,7 +332,7 @@ class TestProtocolsOSPFv3(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ospf6', endsection='^exit', daemon=ospf6_daemon) + frrconfig = self.getFRRconfig('router ospf6', endsection='^exit') self.assertIn(f'router ospf6', frrconfig) self.assertIn(f' graceful-restart grace-period {period}', frrconfig) self.assertIn(f' graceful-restart helper planned-only', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_pim.py b/smoketest/scripts/cli/test_protocols_pim.py index 98b9d57aa..cc62769b3 100755 --- a/smoketest/scripts/cli/test_protocols_pim.py +++ b/smoketest/scripts/cli/test_protocols_pim.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.frrender import pim_daemon @@ -26,6 +27,16 @@ from vyos.utils.process import process_named_running base_path = ['protocols', 'pim'] class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): + @classmethod + def setUpClass(cls): + # call base-classes classmethod + super(TestProtocolsPIM, cls).setUpClass() + # ensure we can also run this test on a live system - so lets clean + # out the current configuration :) + cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + def tearDown(self): # pimd process must be running self.assertTrue(process_named_running(pim_daemon)) @@ -57,11 +68,11 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon) + frrconfig = self.getFRRconfig('router pim', endsection='^exit') self.assertIn(f' rp {rp} {group}', frrconfig) for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip pim', frrconfig) self.assertIn(f' ip pim bfd', frrconfig) @@ -108,7 +119,7 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pimd configuration - frrconfig = self.getFRRconfig('router pim', endsection='^exit', daemon=pim_daemon) + frrconfig = self.getFRRconfig('router pim', endsection='^exit') self.assertIn(f' no send-v6-secondary', frrconfig) self.assertIn(f' rp {rp} {group}', frrconfig) self.assertIn(f' register-suppress-time {register_suppress_time}', frrconfig) @@ -170,11 +181,11 @@ class TestProtocolsPIM(VyOSUnitTestSHIM.TestCase): self.cli_commit() - frrconfig = self.getFRRconfig(daemon=pim_daemon) + frrconfig = self.getFRRconfig() self.assertIn(f'ip igmp watermark-warn {watermark_warning}', frrconfig) for interface in interfaces: - frrconfig = self.getFRRconfig(f'interface {interface}', daemon=pim_daemon) + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', frrconfig) self.assertIn(f' ip igmp', frrconfig) self.assertIn(f' ip igmp version {version}', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_pim6.py b/smoketest/scripts/cli/test_protocols_pim6.py index f69eb4ae1..4ed8fcf7a 100755 --- a/smoketest/scripts/cli/test_protocols_pim6.py +++ b/smoketest/scripts/cli/test_protocols_pim6.py @@ -17,6 +17,8 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section from vyos.frrender import pim6_daemon @@ -34,6 +36,8 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): self.cli_delete(base_path) @@ -52,7 +56,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld', config) self.assertNotIn(f' ipv6 mld version 1', config) @@ -65,7 +69,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld', config) self.assertIn(f' ipv6 mld version 1', config) @@ -88,7 +92,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld join-group ff18::1234', config) @@ -100,7 +104,7 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): # Verify FRR pim6d configuration for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f'interface {interface}', config) self.assertIn(f' ipv6 mld join-group ff38::5678 2001:db8::5678', config) @@ -128,14 +132,14 @@ class TestProtocolsPIMv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR pim6d configuration - config = self.getFRRconfig('router pim6', endsection='^exit', daemon=pim6_daemon) + config = self.getFRRconfig('router pim6', endsection='^exit') self.assertIn(f' join-prune-interval {join_prune_interval}', config) self.assertIn(f' keep-alive-timer {keep_alive_timer}', config) self.assertIn(f' packets {packets}', config) self.assertIn(f' register-suppress-time {register_suppress_time}', config) for interface in interfaces: - config = self.getFRRconfig(f'interface {interface}', daemon=pim6_daemon) + config = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ipv6 pim drpriority {dr_priority}', config) self.assertIn(f' ipv6 pim hello {hello}', config) self.assertIn(f' no ipv6 pim bsm', config) diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py index 33706a9c9..671ef8cd5 100755 --- a/smoketest/scripts/cli/test_protocols_rip.py +++ b/smoketest/scripts/cli/test_protocols_rip.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.ifconfig import Section from vyos.frrender import rip_daemon @@ -39,6 +40,8 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'access-list', acl_in, 'rule', '10', 'source', 'any']) @@ -66,7 +69,7 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router rip') + frrconfig = self.getFRRconfig('router rip', endsection='^exit') self.assertNotIn(f'router rip', frrconfig) # check process health and continuity @@ -116,7 +119,7 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ripd configuration - frrconfig = self.getFRRconfig('router rip') + frrconfig = self.getFRRconfig('router rip', endsection='^exit') self.assertIn(f'router rip', frrconfig) self.assertIn(f' distance {distance}', frrconfig) self.assertIn(f' default-information originate', frrconfig) @@ -175,10 +178,10 @@ class TestProtocolsRIP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR configuration - frrconfig = self.getFRRconfig('router rip') + frrconfig = self.getFRRconfig('router rip', endsection='^exit') self.assertIn(f'version {tx_version}', frrconfig) - frrconfig = self.getFRRconfig(f'interface {interface}') + frrconfig = self.getFRRconfig(f'interface {interface}', endsection='^exit') self.assertIn(f' ip rip receive version {rx_version}', frrconfig) self.assertIn(f' ip rip send version {tx_version}', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py index b10df9679..d2066b825 100755 --- a/smoketest/scripts/cli/test_protocols_ripng.py +++ b/smoketest/scripts/cli/test_protocols_ripng.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2023 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.ifconfig import Section from vyos.frrender import ripng_daemon @@ -40,6 +41,8 @@ class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME cls.cli_set(cls, ['policy', 'access-list6', acl_in, 'rule', '10', 'action', 'permit']) cls.cli_set(cls, ['policy', 'access-list6', acl_in, 'rule', '10', 'source', 'any']) @@ -66,7 +69,7 @@ class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('router ripng') + frrconfig = self.getFRRconfig('router ripng', endsection='^exit') self.assertNotIn(f'router ripng', frrconfig) # check process health and continuity @@ -113,7 +116,7 @@ class TestProtocolsRIPng(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR ospfd configuration - frrconfig = self.getFRRconfig('router ripng') + frrconfig = self.getFRRconfig('router ripng', endsection='^exit') self.assertIn(f'router ripng', frrconfig) self.assertIn(f' default-information originate', frrconfig) self.assertIn(f' default-metric {metric}', frrconfig) diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py index 23738717a..ef2f30d3e 100755 --- a/smoketest/scripts/cli/test_protocols_rpki.py +++ b/smoketest/scripts/cli/test_protocols_rpki.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.frrender import bgp_daemon @@ -111,12 +112,14 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): self.cli_delete(base_path) self.cli_commit() - frrconfig = self.getFRRconfig('rpki') + frrconfig = self.getFRRconfig('rpki', endsection='^exit') self.assertNotIn(f'rpki', frrconfig) # check process health and continuity @@ -153,7 +156,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR configuration - frrconfig = self.getFRRconfig('rpki') + frrconfig = self.getFRRconfig('rpki', endsection='^exit') self.assertIn(f'rpki expire_interval {expire_interval}', frrconfig) self.assertIn(f'rpki polling_period {polling_period}', frrconfig) self.assertIn(f'rpki retry_interval {retry_interval}', frrconfig) @@ -192,7 +195,7 @@ class TestProtocolsRPKI(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify FRR configuration - frrconfig = self.getFRRconfig('rpki') + frrconfig = self.getFRRconfig('rpki', endsection='^exit') for cache_name, cache_config in cache.items(): port = cache_config['port'] preference = cache_config['preference'] diff --git a/smoketest/scripts/cli/test_protocols_segment-routing.py b/smoketest/scripts/cli/test_protocols_segment-routing.py index 624985476..94c808733 100755 --- a/smoketest/scripts/cli/test_protocols_segment-routing.py +++ b/smoketest/scripts/cli/test_protocols_segment-routing.py @@ -17,6 +17,7 @@ import unittest from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.ifconfig import Section @@ -26,6 +27,7 @@ from vyos.utils.system import sysctl_read base_path = ['protocols', 'segment-routing'] + class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -36,6 +38,8 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): # ensure we can also run this test on a live system - so lets clean # out the current configuration :) cls.cli_delete(cls, base_path) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME def tearDown(self): self.cli_delete(base_path) @@ -47,14 +51,64 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): def test_srv6(self): interfaces = Section.interfaces('ethernet', vlan=False) locators = { - 'foo' : { 'prefix' : '2001:a::/64' }, - 'foo' : { 'prefix' : '2001:b::/64', 'usid' : {} }, + 'foo1': {'prefix': '2001:a::/64'}, + 'foo2': {'prefix': '2001:b::/64', 'usid': {}}, + 'foo3': {'prefix': '2001:c::/64', 'format': 'uncompressed-f4024'}, + 'foo4': { + 'prefix': '2001:d::/48', + 'block-len': '32', + 'node-len': '16', + 'func-bits': '16', + 'usid': {}, + 'format': 'usid-f3216', + }, } for locator, locator_config in locators.items(): - self.cli_set(base_path + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']]) + self.cli_set( + base_path + + ['srv6', 'locator', locator, 'prefix', locator_config['prefix']] + ) + if 'block-len' in locator_config: + self.cli_set( + base_path + + [ + 'srv6', + 'locator', + locator, + 'block-len', + locator_config['block-len'], + ] + ) + if 'node-len' in locator_config: + self.cli_set( + base_path + + [ + 'srv6', + 'locator', + locator, + 'node-len', + locator_config['node-len'], + ] + ) + if 'func-bits' in locator_config: + self.cli_set( + base_path + + [ + 'srv6', + 'locator', + locator, + 'func-bits', + locator_config['func-bits'], + ] + ) if 'usid' in locator_config: self.cli_set(base_path + ['srv6', 'locator', locator, 'behavior-usid']) + if 'format' in locator_config: + self.cli_set( + base_path + + ['srv6', 'locator', locator, 'format', locator_config['format']] + ) # verify() - SRv6 should be enabled on at least one interface! with self.assertRaises(ConfigSessionError): @@ -65,16 +119,33 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0') # default - - frrconfig = self.getFRRconfig(f'segment-routing', daemon=zebra_daemon) - self.assertIn(f'segment-routing', frrconfig) - self.assertIn(f' srv6', frrconfig) - self.assertIn(f' locators', frrconfig) + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1' + ) + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '0' + ) # default + + frrconfig = self.getFRRconfig('segment-routing', endsection='^exit') + self.assertIn('segment-routing', frrconfig) + self.assertIn(' srv6', frrconfig) + self.assertIn(' locators', frrconfig) for locator, locator_config in locators.items(): + prefix = locator_config['prefix'] + block_len = locator_config.get('block-len', '40') + node_len = locator_config.get('node-len', '24') + func_bits = locator_config.get('func-bits', '16') + self.assertIn(f' locator {locator}', frrconfig) - self.assertIn(f' prefix {locator_config["prefix"]} block-len 40 node-len 24 func-bits 16', frrconfig) + self.assertIn( + f' prefix {prefix} block-len {block_len} node-len {node_len} func-bits {func_bits}', + frrconfig, + ) + + if 'format' in locator_config: + self.assertIn(f' format {locator_config["format"]}', frrconfig) + if 'usid' in locator_config: + self.assertIn(' behavior usid', frrconfig) def test_srv6_sysctl(self): interfaces = Section.interfaces('ethernet', vlan=False) @@ -86,8 +157,12 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1') # ignore + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1' + ) + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '-1' + ) # ignore # HMAC drop for interface in interfaces: @@ -96,8 +171,12 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.cli_commit() for interface in interfaces: - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1') - self.assertEqual(sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1') # drop + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_enabled'), '1' + ) + self.assertEqual( + sysctl_read(f'net.ipv6.conf.{interface}.seg6_require_hmac'), '1' + ) # drop # Disable SRv6 on first interface first_if = interfaces[-1] @@ -106,5 +185,6 @@ class TestProtocolsSegmentRouting(VyOSUnitTestSHIM.TestCase): self.assertEqual(sysctl_read(f'net.ipv6.conf.{first_if}.seg6_enabled'), '0') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py index a2cde0237..79d6b3af4 100755 --- a/smoketest/scripts/cli/test_protocols_static.py +++ b/smoketest/scripts/cli/test_protocols_static.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021-2024 VyOS maintainers and contributors +# Copyright (C) 2021-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -14,14 +14,20 @@ # 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 os import unittest +from time import sleep from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME from vyos.configsession import ConfigSessionError from vyos.template import is_ipv6 +from vyos.template import get_dhcp_router from vyos.utils.network import get_interface_config from vyos.utils.network import get_vrf_tableid +from vyos.utils.process import process_named_running +from vyos.xml_ref import default_value base_path = ['protocols', 'static'] vrf_path = ['protocols', 'vrf'] @@ -163,16 +169,20 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): super(TestProtocolsStatic, cls).setUpClass() + cls.cli_delete(cls, base_path) cls.cli_delete(cls, ['vrf']) - cls.cli_set(cls, ['vrf', 'name', 'black', 'table', '43210']) + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME @classmethod def tearDownClass(cls): + cls.cli_delete(cls, base_path) cls.cli_delete(cls, ['vrf']) super(TestProtocolsStatic, cls).tearDownClass() def tearDown(self): self.cli_delete(base_path) + self.cli_delete(['vrf']) self.cli_commit() v4route = self.getFRRconfig('ip route', end='') @@ -181,6 +191,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertFalse(v6route) def test_01_static(self): + self.cli_set(['vrf', 'name', 'black', 'table', '43210']) for route, route_config in routes.items(): route_type = 'route' if is_ipv6(route): @@ -315,6 +326,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): self.assertIn(tmp, frrconfig) def test_02_static_table(self): + self.cli_set(['vrf', 'name', 'black', 'table', '43210']) for table in tables: for route, route_config in routes.items(): route_type = 'route' @@ -409,6 +421,7 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): def test_03_static_vrf(self): + self.cli_set(['vrf', 'name', 'black', 'table', '43210']) # Create VRF instances and apply the static routes from above to FRR. # Re-read the configured routes and match them if they are programmed # properly. This also includes VRF leaking @@ -567,5 +580,44 @@ class TestProtocolsStatic(VyOSUnitTestSHIM.TestCase): else: self.assertIn(tmp, frrconfig) + def test_05_dhcp_default_route(self): + # When running via vyos-build under the QEmu environment a local DHCP + # server is available. This test verifies that the default route is set. + # When not running under the VyOS QEMU environment, this test is skipped. + if not os.path.exists('/tmp/vyos.smoketests.hint'): + self.skipTest('Not running under VyOS CI/CD QEMU environment!') + + interface = 'eth0' + interface_path = ['interfaces', 'ethernet', interface] + default_distance = default_value(interface_path + ['dhcp-options', 'default-route-distance']) + self.cli_set(interface_path + ['address', 'dhcp']) + self.cli_commit() + + # Wait for dhclient to receive IP address and default gateway + sleep(5) + + router = get_dhcp_router(interface) + frrconfig = self.getFRRconfig('') + self.assertIn(rf'ip route 0.0.0.0/0 {router} {interface} tag 210 {default_distance}', frrconfig) + + # T6991: Default route is missing when there is no "protocols static" + # CLI node entry + self.cli_delete(base_path) + # We can trigger a FRR reconfiguration and config re-rendering when + # we simply disable IPv6 forwarding + self.cli_set(['system', 'ipv6', 'disable-forwarding']) + self.cli_commit() + + # Re-check FRR configuration that default route is still present + frrconfig = self.getFRRconfig('') + self.assertIn(rf'ip route 0.0.0.0/0 {router} {interface} tag 210 {default_distance}', frrconfig) + + self.cli_delete(interface_path + ['address']) + self.cli_commit() + + # Wait for dhclient to stop + while process_named_running('dhclient', cmdline=interface, timeout=10): + sleep(0.250) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py index 3ce459e44..9fbc931de 100755 --- a/smoketest/scripts/cli/test_service_dns_dynamic.py +++ b/smoketest/scripts/cli/test_service_dns_dynamic.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2019-2024 VyOS maintainers and contributors +# Copyright (C) 2019-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -38,13 +38,17 @@ ttl = '300' interface = 'eth0' class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): - def setUp(self): - # Always start with a clean CLI instance - self.cli_delete(base_path) + @classmethod + def setUpClass(cls): + super(TestServiceDDNS, cls).setUpClass() + + # 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): # Check for running process - self.assertTrue(process_named_running(DDCLIENT_PNAME)) + self.assertTrue(process_named_running(DDCLIENT_PNAME, timeout=10)) # Delete DDNS configuration self.cli_delete(base_path) @@ -336,8 +340,8 @@ class TestServiceDDNS(VyOSUnitTestSHIM.TestCase): # Check for process in VRF systemd_override = cmd(f'cat {DDCLIENT_SYSTEMD_UNIT}') - self.assertIn(f'ExecStart=ip vrf exec {vrf_name} /usr/bin/ddclient -file {DDCLIENT_CONF}', - systemd_override) + self.assertIn(f'ExecStart=ip vrf exec {vrf_name} /usr/bin/ddclient ' \ + f'--file {DDCLIENT_CONF} --foreground', systemd_override) # Check for process in VRF proc = cmd(f'ip vrf pids {vrf_name}') diff --git a/smoketest/scripts/cli/test_service_monitoring_prometheus.py b/smoketest/scripts/cli/test_service_monitoring_prometheus.py index dae103e4b..6e7f8c808 100755 --- a/smoketest/scripts/cli/test_service_monitoring_prometheus.py +++ b/smoketest/scripts/cli/test_service_monitoring_prometheus.py @@ -14,6 +14,7 @@ # 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 os import unittest from base_vyostest_shim import VyOSUnitTestSHIM @@ -22,12 +23,14 @@ from vyos.utils.file import read_file NODE_EXPORTER_PROCESS_NAME = 'node_exporter' FRR_EXPORTER_PROCESS_NAME = 'frr_exporter' +BLACKBOX_EXPORTER_PROCESS_NAME = 'blackbox_exporter' base_path = ['service', 'monitoring', 'prometheus'] listen_if = 'dum3421' listen_ip = '192.0.2.1' node_exporter_service_file = '/etc/systemd/system/node_exporter.service' frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service' +blackbox_exporter_service_file = '/etc/systemd/system/blackbox_exporter.service' class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase): @@ -53,6 +56,7 @@ class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase): def test_01_node_exporter(self): self.cli_set(base_path + ['node-exporter', 'listen-address', listen_ip]) + self.cli_set(base_path + ['node-exporter', 'collectors', 'textfile']) # commit changes self.cli_commit() @@ -60,6 +64,11 @@ class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase): file_content = read_file(node_exporter_service_file) self.assertIn(f'{listen_ip}:9100', file_content) + self.assertTrue(os.path.isdir('/run/node_exporter/collector')) + self.assertIn( + '--collector.textfile.directory=/run/node_exporter/collector', file_content + ) + # Check for running process self.assertTrue(process_named_running(NODE_EXPORTER_PROCESS_NAME)) @@ -75,6 +84,78 @@ class TestMonitoringPrometheus(VyOSUnitTestSHIM.TestCase): # Check for running process self.assertTrue(process_named_running(FRR_EXPORTER_PROCESS_NAME)) + def test_03_blackbox_exporter(self): + self.cli_set(base_path + ['blackbox-exporter', 'listen-address', listen_ip]) + + # commit changes + self.cli_commit() + + file_content = read_file(blackbox_exporter_service_file) + self.assertIn(f'{listen_ip}:9115', file_content) + + # Check for running process + self.assertTrue(process_named_running(BLACKBOX_EXPORTER_PROCESS_NAME)) + + def test_04_blackbox_exporter_with_config(self): + self.cli_set(base_path + ['blackbox-exporter', 'listen-address', listen_ip]) + self.cli_set( + base_path + + [ + 'blackbox-exporter', + 'modules', + 'dns', + 'name', + 'dns_ip4', + 'preferred-ip-protocol', + 'ipv4', + ] + ) + self.cli_set( + base_path + + [ + 'blackbox-exporter', + 'modules', + 'dns', + 'name', + 'dns_ip4', + 'query-name', + 'vyos.io', + ] + ) + self.cli_set( + base_path + + [ + 'blackbox-exporter', + 'modules', + 'dns', + 'name', + 'dns_ip4', + 'query-type', + 'A', + ] + ) + self.cli_set( + base_path + + [ + 'blackbox-exporter', + 'modules', + 'icmp', + 'name', + 'icmp_ip6', + 'preferred-ip-protocol', + 'ipv6', + ] + ) + + # commit changes + self.cli_commit() + + file_content = read_file(blackbox_exporter_service_file) + self.assertIn(f'{listen_ip}:9115', file_content) + + # Check for running process + self.assertTrue(process_named_running(BLACKBOX_EXPORTER_PROCESS_NAME)) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py index d8e325eee..fa08a5b32 100755 --- a/smoketest/scripts/cli/test_service_ssh.py +++ b/smoketest/scripts/cli/test_service_ssh.py @@ -33,16 +33,32 @@ from vyos.xml_ref import default_value PROCESS_NAME = 'sshd' SSHD_CONF = '/run/sshd/sshd_config' base_path = ['service', 'ssh'] +pki_path = ['pki'] key_rsa = '/etc/ssh/ssh_host_rsa_key' key_dsa = '/etc/ssh/ssh_host_dsa_key' key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' +trusted_user_ca_key = '/etc/ssh/trusted_user_ca_key' + def get_config_value(key): tmp = read_file(SSHD_CONF) tmp = re.findall(f'\n?{key}\s+(.*)', tmp) return tmp + +ca_root_cert_data = """ +MIIBcTCCARagAwIBAgIUDcAf1oIQV+6WRaW7NPcSnECQ/lUwCgYIKoZIzj0EAwIw +HjEcMBoGA1UEAwwTVnlPUyBzZXJ2ZXIgcm9vdCBDQTAeFw0yMjAyMTcxOTQxMjBa +Fw0zMjAyMTUxOTQxMjBaMB4xHDAaBgNVBAMME1Z5T1Mgc2VydmVyIHJvb3QgQ0Ew +WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ0y24GzKQf4aM2Ir12tI9yITOIzAUj +ZXyJeCmYI6uAnyAMqc4Q4NKyfq3nBi4XP87cs1jlC1P2BZ8MsjL5MdGWozIwMDAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRwC/YaieMEnjhYa7K3Flw/o0SFuzAK +BggqhkjOPQQDAgNJADBGAiEAh3qEj8vScsjAdBy5shXzXDVVOKWCPTdGrPKnu8UW +a2cCIQDlDgkzWmn5ujc5ATKz1fj+Se/aeqwh4QyoWCVTFLIxhQ== +""" + + class TestServiceSSH(VyOSUnitTestSHIM.TestCase): @classmethod def setUpClass(cls): @@ -98,27 +114,27 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # Check configured port port = get_config_value('Port')[0] - self.assertTrue("1234" in port) + self.assertTrue('1234' in port) # Check DNS usage dns = get_config_value('UseDNS')[0] - self.assertTrue("no" in dns) + self.assertTrue('no' in dns) # Check PasswordAuthentication pwd = get_config_value('PasswordAuthentication')[0] - self.assertTrue("no" in pwd) + self.assertTrue('no' in pwd) # Check loglevel loglevel = get_config_value('LogLevel')[0] - self.assertTrue("VERBOSE" in loglevel) + self.assertTrue('VERBOSE' in loglevel) # Check listen address address = get_config_value('ListenAddress')[0] - self.assertTrue("127.0.0.1" in address) + self.assertTrue('127.0.0.1' in address) # Check keepalive keepalive = get_config_value('ClientAliveInterval')[0] - self.assertTrue("100" in keepalive) + self.assertTrue('100' in keepalive) def test_ssh_multiple_listen_addresses(self): # Check if SSH service can be configured and runs with multiple @@ -197,7 +213,17 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): test_command = 'uname -a' self.cli_set(base_path) - self.cli_set(['system', 'login', 'user', test_user, 'authentication', 'plaintext-password', test_pass]) + self.cli_set( + [ + 'system', + 'login', + 'user', + test_user, + 'authentication', + 'plaintext-password', + test_pass, + ] + ) # commit changes self.cli_commit() @@ -210,7 +236,9 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # Login with invalid credentials with self.assertRaises(paramiko.ssh_exception.AuthenticationException): - output, error = self.ssh_send_cmd(test_command, 'invalid_user', 'invalid_password') + output, error = self.ssh_send_cmd( + test_command, 'invalid_user', 'invalid_password' + ) self.cli_delete(['system', 'login', 'user', test_user]) self.cli_commit() @@ -250,7 +278,7 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): sshguard_lines = [ f'THRESHOLD={threshold}', f'BLOCK_TIME={block_time}', - f'DETECTION_TIME={detect_time}' + f'DETECTION_TIME={detect_time}', ] tmp_sshguard_conf = read_file(SSHGUARD_CONFIG) @@ -268,12 +296,16 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): self.assertFalse(process_named_running(SSHGUARD_PROCESS)) - # Network Device Collaborative Protection Profile def test_ssh_ndcpp(self): ciphers = ['aes128-cbc', 'aes128-ctr', 'aes256-cbc', 'aes256-ctr'] host_key_algs = ['sk-ssh-ed25519@openssh.com', 'ssh-rsa', 'ssh-ed25519'] - kexes = ['diffie-hellman-group14-sha1', 'ecdh-sha2-nistp256', 'ecdh-sha2-nistp384', 'ecdh-sha2-nistp521'] + kexes = [ + 'diffie-hellman-group14-sha1', + 'ecdh-sha2-nistp256', + 'ecdh-sha2-nistp384', + 'ecdh-sha2-nistp521', + ] macs = ['hmac-sha1', 'hmac-sha2-256', 'hmac-sha2-512'] rekey_time = '60' rekey_data = '1024' @@ -293,22 +325,29 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): # commit changes self.cli_commit() - ssh_lines = ['Ciphers aes128-cbc,aes128-ctr,aes256-cbc,aes256-ctr', - 'HostKeyAlgorithms sk-ssh-ed25519@openssh.com,ssh-rsa,ssh-ed25519', - 'MACs hmac-sha1,hmac-sha2-256,hmac-sha2-512', - 'KexAlgorithms diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521', - 'RekeyLimit 1024M 60M' - ] + ssh_lines = [ + 'Ciphers aes128-cbc,aes128-ctr,aes256-cbc,aes256-ctr', + 'HostKeyAlgorithms sk-ssh-ed25519@openssh.com,ssh-rsa,ssh-ed25519', + 'MACs hmac-sha1,hmac-sha2-256,hmac-sha2-512', + 'KexAlgorithms diffie-hellman-group14-sha1,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521', + 'RekeyLimit 1024M 60M', + ] tmp_sshd_conf = read_file(SSHD_CONF) for line in ssh_lines: self.assertIn(line, tmp_sshd_conf) def test_ssh_pubkey_accepted_algorithm(self): - algs = ['ssh-ed25519', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', - 'ecdsa-sha2-nistp521', 'ssh-dss', 'ssh-rsa', 'rsa-sha2-256', - 'rsa-sha2-512' - ] + algs = [ + 'ssh-ed25519', + 'ecdsa-sha2-nistp256', + 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', + 'ssh-dss', + 'ssh-rsa', + 'rsa-sha2-256', + 'rsa-sha2-512', + ] expected = 'PubkeyAcceptedAlgorithms ' for alg in algs: @@ -320,6 +359,40 @@ class TestServiceSSH(VyOSUnitTestSHIM.TestCase): tmp_sshd_conf = read_file(SSHD_CONF) self.assertIn(expected, tmp_sshd_conf) + def test_ssh_trusted_user_ca_key(self): + ca_cert_name = 'test_ca' + + # set pki ca <ca_cert_name> certificate <ca_key_data> + # set service ssh trusted-user-ca-key ca-certificate <ca_cert_name> + self.cli_set( + pki_path + + [ + 'ca', + ca_cert_name, + 'certificate', + ca_root_cert_data.replace('\n', ''), + ] + ) + self.cli_set( + base_path + ['trusted-user-ca-key', 'ca-certificate', ca_cert_name] + ) + self.cli_commit() + + trusted_user_ca_key_config = get_config_value('TrustedUserCAKeys') + self.assertIn(trusted_user_ca_key, trusted_user_ca_key_config) + + with open(trusted_user_ca_key, 'r') as file: + ca_key_contents = file.read() + self.assertIn(ca_root_cert_data, ca_key_contents) + + self.cli_delete(base_path + ['trusted-user-ca-key']) + self.cli_delete(['pki', 'ca', ca_cert_name]) + self.cli_commit() + + # Verify the CA key is removed + trusted_user_ca_key_config = get_config_value('TrustedUserCAKeys') + self.assertNotIn(trusted_user_ca_key, trusted_user_ca_key_config) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py index 7d730f7b2..5b6ef2046 100755 --- a/smoketest/scripts/cli/test_system_ip.py +++ b/smoketest/scripts/cli/test_system_ip.py @@ -20,8 +20,6 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.system import sysctl_read from vyos.xml_ref import default_value -from vyos.frrender import mgmt_daemon -from vyos.frrender import zebra_daemon base_path = ['system', 'ip'] @@ -45,13 +43,13 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '0') - frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertIn('no ip forwarding', frrconfig) self.cli_delete(base_path + ['disable-forwarding']) self.cli_commit() self.assertEqual(sysctl_read('net.ipv4.conf.all.forwarding'), '1') - frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertNotIn('no ip forwarding', frrconfig) def test_system_ip_multipath(self): @@ -91,7 +89,7 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('ip protocol', end='') for protocol in protocols: self.assertIn(f'ip protocol {protocol} route-map route-map-{protocol}', frrconfig) @@ -102,7 +100,7 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ip protocol', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('ip protocol', end='') self.assertNotIn(f'ip protocol', frrconfig) def test_system_ip_protocol_non_existing_route_map(self): @@ -121,13 +119,13 @@ class TestSystemIP(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config applied to FRR - frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertIn(f'no ip nht resolve-via-default', frrconfig) self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config removed to FRR - frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertNotIn(f'no ip nht resolve-via-default', frrconfig) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py index ebf620204..26f281bb4 100755 --- a/smoketest/scripts/cli/test_system_ipv6.py +++ b/smoketest/scripts/cli/test_system_ipv6.py @@ -21,8 +21,6 @@ from base_vyostest_shim import VyOSUnitTestSHIM from vyos.configsession import ConfigSessionError from vyos.utils.system import sysctl_read from vyos.xml_ref import default_value -from vyos.frrender import mgmt_daemon -from vyos.frrender import zebra_daemon base_path = ['system', 'ipv6'] @@ -46,13 +44,13 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['disable-forwarding']) self.cli_commit() self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '0') - frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertIn('no ipv6 forwarding', frrconfig) self.cli_delete(base_path + ['disable-forwarding']) self.cli_commit() self.assertEqual(sysctl_read('net.ipv6.conf.all.forwarding'), '1') - frrconfig = self.getFRRconfig('', end='', daemon=zebra_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertNotIn('no ipv6 forwarding', frrconfig) def test_system_ipv6_strict_dad(self): @@ -109,7 +107,7 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('ipv6 protocol', end='') for protocol in protocols: # VyOS and FRR use a different name for OSPFv3 (IPv6) if protocol == 'ospfv3': @@ -123,7 +121,7 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_commit() # Verify route-map properly applied to FRR - frrconfig = self.getFRRconfig('ipv6 protocol', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('ipv6 protocol', end='') self.assertNotIn(f'ipv6 protocol', frrconfig) def test_system_ipv6_protocol_non_existing_route_map(self): @@ -142,13 +140,13 @@ class TestSystemIPv6(VyOSUnitTestSHIM.TestCase): self.cli_set(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config applied to FRR - frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertIn(f'no ipv6 nht resolve-via-default', frrconfig) self.cli_delete(base_path + ['nht', 'no-resolve-via-default']) self.cli_commit() # Verify CLI config removed to FRR - frrconfig = self.getFRRconfig('', end='', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig('', end='') self.assertNotIn(f'no ipv6 nht resolve-via-default', frrconfig) if __name__ == '__main__': diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py index f4ed1a61f..30980f9ec 100755 --- a/smoketest/scripts/cli/test_vrf.py +++ b/smoketest/scripts/cli/test_vrf.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (C) 2020-2024 VyOS maintainers and contributors +# Copyright (C) 2020-2025 VyOS maintainers and contributors # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or later as @@ -18,12 +18,13 @@ import re import os import unittest -from base_vyostest_shim import VyOSUnitTestSHIM from json import loads from jmespath import search +from base_vyostest_shim import VyOSUnitTestSHIM +from base_vyostest_shim import CSTORE_GUARD_TIME + from vyos.configsession import ConfigSessionError -from vyos.frrender import mgmt_daemon from vyos.ifconfig import Interface from vyos.ifconfig import Section from vyos.utils.file import read_file @@ -52,6 +53,10 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): else: for tmp in Section.interfaces('ethernet', vlan=False): cls._interfaces.append(tmp) + + # Enable CSTORE guard time required by FRR related tests + cls._commit_guard_time = CSTORE_GUARD_TIME + # call base-classes classmethod super(VRFTest, cls).setUpClass() @@ -113,7 +118,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): regex = f'{table}\s+{vrf}\s+#\s+{description}' self.assertTrue(re.findall(regex, iproute2_config)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) self.assertEqual(int(table), get_vrf_tableid(vrf)) @@ -234,7 +239,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) self.assertIn(f' ip route {prefix} {next_hop}', frrconfig) @@ -318,7 +323,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f'vrf {vrf}', frrconfig) for protocol in v4_protocols: self.assertIn(f' ip protocol {protocol} route-map route-map-{vrf}-{protocol}', frrconfig) @@ -333,7 +338,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertNotIn(f' ip protocol', frrconfig) def test_vrf_ip_ipv6_protocol_non_existing_route_map(self): @@ -381,7 +386,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f'vrf {vrf}', frrconfig) for protocol in v6_protocols: # VyOS and FRR use a different name for OSPFv3 (IPv6) @@ -400,7 +405,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertNotIn(f' ipv6 protocol', frrconfig) def test_vrf_vni_duplicates(self): @@ -430,7 +435,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 1) @@ -452,7 +457,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 1) @@ -475,7 +480,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 2) @@ -495,7 +500,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) # Increment table ID for the next run table = str(int(table) + 2) @@ -503,7 +508,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify purple VRF/VNI self.assertTrue(interface_exists(purple)) table = str(int(table) + 10) - frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {purple}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) # Now delete all the VNIs @@ -518,12 +523,12 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): for vrf in vrfs: self.assertTrue(interface_exists(vrf)) - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertNotIn('vni', frrconfig) # Verify purple VNI remains self.assertTrue(interface_exists(purple)) - frrconfig = self.getFRRconfig(f'vrf {purple}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {purple}', endsection='^exit-vrf') self.assertIn(f' vni {table}', frrconfig) def test_vrf_ip_ipv6_nht(self): @@ -541,7 +546,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly applied to FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertIn(f'vrf {vrf}', frrconfig) self.assertIn(f' no ip nht resolve-via-default', frrconfig) self.assertIn(f' no ipv6 nht resolve-via-default', frrconfig) @@ -556,7 +561,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase): # Verify route-map properly is removed from FRR for vrf in vrfs: - frrconfig = self.getFRRconfig(f'vrf {vrf}', daemon=mgmt_daemon) + frrconfig = self.getFRRconfig(f'vrf {vrf}', endsection='^exit-vrf') self.assertNotIn(f' no ip nht resolve-via-default', frrconfig) self.assertNotIn(f' no ipv6 nht resolve-via-default', frrconfig) diff --git a/src/conf_mode/firewall.py b/src/conf_mode/firewall.py index ffbd915a2..768bb127d 100755 --- a/src/conf_mode/firewall.py +++ b/src/conf_mode/firewall.py @@ -18,7 +18,6 @@ import os import re from sys import exit - from vyos.base import Warning from vyos.config import Config from vyos.configdict import is_node_changed @@ -34,6 +33,8 @@ from vyos.utils.dict import dict_search_recursive from vyos.utils.process import call from vyos.utils.process import cmd from vyos.utils.process import rc_cmd +from vyos.utils.network import get_vrf_members +from vyos.utils.network import get_interface_vrf from vyos import ConfigError from vyos import airbag from pathlib import Path @@ -43,7 +44,6 @@ airbag.enable() nftables_conf = '/run/nftables.conf' domain_resolver_usage = '/run/use-vyos-domain-resolver-firewall' -domain_resolver_usage_nat = '/run/use-vyos-domain-resolver-nat' sysctl_file = r'/run/sysctl/10-vyos-firewall.conf' @@ -134,6 +134,27 @@ def get_config(config=None): fqdn_config_parse(firewall, 'firewall') + if not os.path.exists(nftables_conf): + firewall['first_install'] = True + + if 'zone' in firewall: + for local_zone, local_zone_conf in firewall['zone'].items(): + if 'local_zone' not in local_zone_conf: + # Get physical interfaces assigned to the zone if vrf is used: + if 'vrf' in local_zone_conf['member']: + local_zone_conf['vrf_interfaces'] = {} + for vrf_name in local_zone_conf['member']['vrf']: + local_zone_conf['vrf_interfaces'][vrf_name] = ','.join(get_vrf_members(vrf_name)) + continue + + local_zone_conf['from_local'] = {} + + for zone, zone_conf in firewall['zone'].items(): + if zone == local_zone or 'from' not in zone_conf: + continue + if local_zone in zone_conf['from']: + local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] + set_dependents('conntrack', conf) return firewall @@ -442,28 +463,45 @@ def verify(firewall): local_zone = False zone_interfaces = [] + zone_vrf = [] if 'zone' in firewall: for zone, zone_conf in firewall['zone'].items(): - if 'local_zone' not in zone_conf and 'interface' not in zone_conf: + if 'local_zone' not in zone_conf and 'member' not in zone_conf: raise ConfigError(f'Zone "{zone}" has no interfaces and is not the local zone') if 'local_zone' in zone_conf: if local_zone: raise ConfigError('There cannot be multiple local zones') - if 'interface' in zone_conf: + if 'member' in zone_conf: raise ConfigError('Local zone cannot have interfaces assigned') if 'intra_zone_filtering' in zone_conf: raise ConfigError('Local zone cannot use intra-zone-filtering') local_zone = True - if 'interface' in zone_conf: - found_duplicates = [intf for intf in zone_conf['interface'] if intf in zone_interfaces] + if 'member' in zone_conf: + if 'interface' in zone_conf['member']: + for iface in zone_conf['member']['interface']: + + if iface in zone_interfaces: + raise ConfigError(f'Interfaces cannot be assigned to multiple zones') - if found_duplicates: - raise ConfigError(f'Interfaces cannot be assigned to multiple zones') + iface_vrf = get_interface_vrf(iface) + if iface_vrf != 'default': + Warning(f"Interface {iface} assigned to zone {zone} is in VRF {iface_vrf}. This might not work as expected.") + zone_interfaces.append(iface) - zone_interfaces += zone_conf['interface'] + if 'vrf' in zone_conf['member']: + for vrf in zone_conf['member']['vrf']: + if vrf in zone_vrf: + raise ConfigError(f'VRF cannot be assigned to multiple zones') + zone_vrf.append(vrf) + + if 'vrf_interfaces' in zone_conf: + for vrf_name, vrf_interfaces in zone_conf['vrf_interfaces'].items(): + if not vrf_interfaces: + raise ConfigError( + f'VRF "{vrf_name}" cannot be a member of any zone. It does not contain any interfaces.') if 'intra_zone_filtering' in zone_conf: intra_zone = zone_conf['intra_zone_filtering'] @@ -499,22 +537,6 @@ def verify(firewall): return None def generate(firewall): - if not os.path.exists(nftables_conf): - firewall['first_install'] = True - - if 'zone' in firewall: - for local_zone, local_zone_conf in firewall['zone'].items(): - if 'local_zone' not in local_zone_conf: - continue - - local_zone_conf['from_local'] = {} - - for zone, zone_conf in firewall['zone'].items(): - if zone == local_zone or 'from' not in zone_conf: - continue - if local_zone in zone_conf['from']: - local_zone_conf['from_local'][zone] = zone_conf['from'][local_zone] - render(nftables_conf, 'firewall/nftables.j2', firewall) render(sysctl_file, 'firewall/sysctl-firewall.conf.j2', firewall) return None diff --git a/src/conf_mode/interfaces_openvpn.py b/src/conf_mode/interfaces_openvpn.py index 8c1213e2b..a9b4e570d 100755 --- a/src/conf_mode/interfaces_openvpn.py +++ b/src/conf_mode/interfaces_openvpn.py @@ -32,6 +32,7 @@ from vyos.base import DeprecationWarning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import is_node_changed +from vyos.configdiff import get_config_diff from vyos.configverify import verify_vrf from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mirror_redirect @@ -94,6 +95,23 @@ def get_config(config=None): if 'deleted' in openvpn: return openvpn + if not is_node_changed(conf, base) and dict_search_args(openvpn, 'tls'): + diff = get_config_diff(conf) + if diff.get_child_nodes_diff(['pki'], recursive=True).get('add') == ['ca', 'certificate']: + crl_path = os.path.join(cfg_dir, f'{ifname}_crl.pem') + if os.path.exists(crl_path): + # do not restart service when changed only CRL and crl file already exist + openvpn.update({'no_restart_crl': True}) + for rec in diff.get_child_nodes_diff(['pki', 'ca'], recursive=True).get('add'): + if diff.get_child_nodes_diff(['pki', 'ca', rec], recursive=True).get('add') != ['crl']: + openvpn.update({'no_restart_crl': False}) + break + if openvpn.get('no_restart_crl'): + for rec in diff.get_child_nodes_diff(['pki', 'certificate'], recursive=True).get('add'): + if diff.get_child_nodes_diff(['pki', 'certificate', rec], recursive=True).get('add') != ['revoke']: + openvpn.update({'no_restart_crl': False}) + break + if is_node_changed(conf, base + [ifname, 'openvpn-option']): openvpn.update({'restart_required': {}}) if is_node_changed(conf, base + [ifname, 'enable-dco']): @@ -786,10 +804,12 @@ def apply(openvpn): # No matching OpenVPN process running - maybe it got killed or none # existed - nevertheless, spawn new OpenVPN process - action = 'reload-or-restart' - if 'restart_required' in openvpn: - action = 'restart' - call(f'systemctl {action} openvpn@{interface}.service') + + if not openvpn.get('no_restart_crl'): + action = 'reload-or-restart' + if 'restart_required' in openvpn: + action = 'restart' + call(f'systemctl {action} openvpn@{interface}.service') o = VTunIf(**openvpn) o.update(openvpn) diff --git a/src/conf_mode/service_monitoring_prometheus.py b/src/conf_mode/service_monitoring_prometheus.py index e0a9fc4ef..9a07d8593 100755 --- a/src/conf_mode/service_monitoring_prometheus.py +++ b/src/conf_mode/service_monitoring_prometheus.py @@ -26,15 +26,18 @@ from vyos.utils.process import call from vyos import ConfigError from vyos import airbag - airbag.enable() node_exporter_service_file = '/etc/systemd/system/node_exporter.service' node_exporter_systemd_service = 'node_exporter.service' +node_exporter_collector_path = '/run/node_exporter/collector' frr_exporter_service_file = '/etc/systemd/system/frr_exporter.service' frr_exporter_systemd_service = 'frr_exporter.service' +blackbox_exporter_service_file = '/etc/systemd/system/blackbox_exporter.service' +blackbox_exporter_systemd_service = 'blackbox_exporter.service' + def get_config(config=None): if config: @@ -57,6 +60,12 @@ def get_config(config=None): if tmp: monitoring.update({'frr_exporter_restart_required': {}}) + tmp = False + for node in ['vrf', 'config-file']: + tmp = tmp or is_node_changed(conf, base + ['blackbox-exporter', node]) + if tmp: + monitoring.update({'blackbox_exporter_restart_required': {}}) + return monitoring @@ -70,6 +79,22 @@ def verify(monitoring): if 'frr_exporter' in monitoring: verify_vrf(monitoring['frr_exporter']) + if 'blackbox_exporter' in monitoring: + verify_vrf(monitoring['blackbox_exporter']) + + if ( + 'modules' in monitoring['blackbox_exporter'] + and 'dns' in monitoring['blackbox_exporter']['modules'] + and 'name' in monitoring['blackbox_exporter']['modules']['dns'] + ): + for mod_name, mod_config in monitoring['blackbox_exporter']['modules'][ + 'dns' + ]['name'].items(): + if 'query_name' not in mod_config: + raise ConfigError( + f'query name not specified in dns module {mod_name}' + ) + return None @@ -84,6 +109,11 @@ def generate(monitoring): if os.path.isfile(frr_exporter_service_file): os.unlink(frr_exporter_service_file) + if not monitoring or 'blackbox_exporter' not in monitoring: + # Delete systemd files + if os.path.isfile(blackbox_exporter_service_file): + os.unlink(blackbox_exporter_service_file) + if not monitoring: return None @@ -94,6 +124,13 @@ def generate(monitoring): 'prometheus/node_exporter.service.j2', monitoring['node_exporter'], ) + if ( + 'collectors' in monitoring['node_exporter'] + and 'textfile' in monitoring['node_exporter']['collectors'] + ): + # Create textcollector folder + if not os.path.isdir(node_exporter_collector_path): + os.makedirs(node_exporter_collector_path) if 'frr_exporter' in monitoring: # Render frr_exporter service_file @@ -103,6 +140,20 @@ def generate(monitoring): monitoring['frr_exporter'], ) + if 'blackbox_exporter' in monitoring: + # Render blackbox_exporter service_file + render( + blackbox_exporter_service_file, + 'prometheus/blackbox_exporter.service.j2', + monitoring['blackbox_exporter'], + ) + # Render blackbox_exporter config file + render( + '/run/blackbox_exporter/config.yml', + 'prometheus/blackbox_exporter.yml.j2', + monitoring['blackbox_exporter'], + ) + return None @@ -113,6 +164,8 @@ def apply(monitoring): call(f'systemctl stop {node_exporter_systemd_service}') if not monitoring or 'frr_exporter' not in monitoring: call(f'systemctl stop {frr_exporter_systemd_service}') + if not monitoring or 'blackbox_exporter' not in monitoring: + call(f'systemctl stop {blackbox_exporter_systemd_service}') if not monitoring: return @@ -133,6 +186,14 @@ def apply(monitoring): call(f'systemctl {systemd_action} {frr_exporter_systemd_service}') + if 'blackbox_exporter' in monitoring: + # we need to restart the service if e.g. the VRF name changed + systemd_action = 'reload-or-restart' + if 'blackbox_exporter_restart_required' in monitoring: + systemd_action = 'restart' + + call(f'systemctl {systemd_action} {blackbox_exporter_systemd_service}') + if __name__ == '__main__': try: diff --git a/src/conf_mode/service_ssh.py b/src/conf_mode/service_ssh.py index 9abdd33dc..759f87bb2 100755 --- a/src/conf_mode/service_ssh.py +++ b/src/conf_mode/service_ssh.py @@ -23,10 +23,16 @@ from syslog import LOG_INFO from vyos.config import Config from vyos.configdict import is_node_changed from vyos.configverify import verify_vrf +from vyos.configverify import verify_pki_ca_certificate from vyos.utils.process import call from vyos.template import render from vyos import ConfigError from vyos import airbag +from vyos.pki import find_chain +from vyos.pki import encode_certificate +from vyos.pki import load_certificate +from vyos.utils.file import write_file + airbag.enable() config_file = r'/run/sshd/sshd_config' @@ -38,6 +44,9 @@ key_rsa = '/etc/ssh/ssh_host_rsa_key' key_dsa = '/etc/ssh/ssh_host_dsa_key' key_ed25519 = '/etc/ssh/ssh_host_ed25519_key' +trusted_user_ca_key = '/etc/ssh/trusted_user_ca_key' + + def get_config(config=None): if config: conf = config @@ -47,10 +56,13 @@ def get_config(config=None): if not conf.exists(base): return None - ssh = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True) + ssh = conf.get_config_dict( + base, key_mangling=('-', '_'), get_first_key=True, with_pki=True + ) tmp = is_node_changed(conf, base + ['vrf']) - if tmp: ssh.update({'restart_required': {}}) + if tmp: + ssh.update({'restart_required': {}}) # We have gathered the dict representation of the CLI, but there are default # options which we need to update into the dictionary retrived. @@ -62,20 +74,32 @@ def get_config(config=None): # Ignore default XML values if config doesn't exists # Delete key from dict if not conf.exists(base + ['dynamic-protection']): - del ssh['dynamic_protection'] + del ssh['dynamic_protection'] return ssh + def verify(ssh): if not ssh: return None if 'rekey' in ssh and 'data' not in ssh['rekey']: - raise ConfigError(f'Rekey data is required!') + raise ConfigError('Rekey data is required!') + + if 'trusted_user_ca_key' in ssh: + if 'ca_certificate' not in ssh['trusted_user_ca_key']: + raise ConfigError('CA certificate is required for TrustedUserCAKey') + + ca_key_name = ssh['trusted_user_ca_key']['ca_certificate'] + verify_pki_ca_certificate(ssh, ca_key_name) + pki_ca_cert = ssh['pki']['ca'][ca_key_name] + if 'certificate' not in pki_ca_cert or not pki_ca_cert['certificate']: + raise ConfigError(f"CA certificate '{ca_key_name}' is not valid or missing") verify_vrf(ssh) return None + def generate(ssh): if not ssh: if os.path.isfile(config_file): @@ -95,6 +119,24 @@ def generate(ssh): syslog(LOG_INFO, 'SSH ed25519 host key not found, generating new key!') call(f'ssh-keygen -q -N "" -t ed25519 -f {key_ed25519}') + if 'trusted_user_ca_key' in ssh: + ca_key_name = ssh['trusted_user_ca_key']['ca_certificate'] + pki_ca_cert = ssh['pki']['ca'][ca_key_name] + + loaded_ca_cert = load_certificate(pki_ca_cert['certificate']) + loaded_ca_certs = { + load_certificate(c['certificate']) + for c in ssh['pki']['ca'].values() + if 'certificate' in c + } + + ca_full_chain = find_chain(loaded_ca_cert, loaded_ca_certs) + write_file( + trusted_user_ca_key, '\n'.join(encode_certificate(c) for c in ca_full_chain) + ) + elif os.path.exists(trusted_user_ca_key): + os.unlink(trusted_user_ca_key) + render(config_file, 'ssh/sshd_config.j2', ssh) if 'dynamic_protection' in ssh: @@ -103,12 +145,12 @@ def generate(ssh): return None + def apply(ssh): - systemd_service_ssh = 'ssh.service' systemd_service_sshguard = 'sshguard.service' if not ssh: # SSH access is removed in the commit - call(f'systemctl stop ssh@*.service') + call('systemctl stop ssh@*.service') call(f'systemctl stop {systemd_service_sshguard}') return None @@ -122,13 +164,14 @@ def apply(ssh): if 'restart_required' in ssh: # this is only true if something for the VRFs changed, thus we # stop all VRF services and only restart then new ones - call(f'systemctl stop ssh@*.service') + call('systemctl stop ssh@*.service') systemd_action = 'restart' for vrf in ssh['vrf']: call(f'systemctl {systemd_action} ssh@{vrf}.service') return None + if __name__ == '__main__': try: c = get_config() diff --git a/src/etc/skel/.bashrc b/src/etc/skel/.bashrc index ba7d50003..f807f0c72 100644 --- a/src/etc/skel/.bashrc +++ b/src/etc/skel/.bashrc @@ -92,6 +92,9 @@ fi #alias la='ls -A' #alias l='ls -CF' +# Disable iproute2 auto color +alias ip="ip --color=never" + # Alias definitions. # You may want to put all your additions into a separate file like # ~/.bash_aliases, instead of adding them here directly. diff --git a/src/etc/udev/rules.d/90-vyos-serial.rules b/src/etc/udev/rules.d/90-vyos-serial.rules index 30c1d3170..f86b2258f 100644 --- a/src/etc/udev/rules.d/90-vyos-serial.rules +++ b/src/etc/udev/rules.d/90-vyos-serial.rules @@ -8,7 +8,7 @@ SUBSYSTEMS=="pci", IMPORT{builtin}="hwdb --subsystem=pci" SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" # /dev/serial/by-path/, /dev/serial/by-id/ for USB devices -KERNEL!="ttyUSB[0-9]*", GOTO="serial_end" +KERNEL!="ttyUSB[0-9]*|ttyACM[0-9]*", GOTO="serial_end" SUBSYSTEMS=="usb-serial", ENV{.ID_PORT}="$attr{port_number}" diff --git a/src/migration-scripts/firewall/16-to-17 b/src/migration-scripts/firewall/16-to-17 index ad0706f04..ad0706f04 100755..100644 --- a/src/migration-scripts/firewall/16-to-17 +++ b/src/migration-scripts/firewall/16-to-17 diff --git a/src/migration-scripts/firewall/17-to-18 b/src/migration-scripts/firewall/17-to-18 new file mode 100755 index 000000000..34ce6aa07 --- /dev/null +++ b/src/migration-scripts/firewall/17-to-18 @@ -0,0 +1,41 @@ +# Copyright (C) 2024-2025 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library. If not, see <http://www.gnu.org/licenses/>. + +# From +# set firewall zone <zone> interface RED +# set firewall zone <zone> interface eth0 +# To +# set firewall zone <zone> member vrf RED +# set firewall zone <zone> member interface eth0 + +from vyos.configtree import ConfigTree + +base = ['firewall', 'zone'] + +def migrate(config: ConfigTree) -> None: + if not config.exists(base): + # Nothing to do + return + + for zone in config.list_nodes(base): + zone_iface_base = base + [zone, 'interface'] + zone_member_base = base + [zone, 'member'] + if config.exists(zone_iface_base): + for iface in config.return_values(zone_iface_base): + if config.exists(['vrf', 'name', iface]): + config.set(zone_member_base + ['vrf'], value=iface, replace=False) + else: + config.set(zone_member_base + ['interface'], value=iface, replace=False) + config.delete(zone_iface_base) diff --git a/src/op_mode/tech_support.py b/src/op_mode/tech_support.py index f60bb87ff..24ac0af1b 100644 --- a/src/op_mode/tech_support.py +++ b/src/op_mode/tech_support.py @@ -97,21 +97,22 @@ def _get_boot_config(): return strip_config_source(config) def _get_config_scripts(): - from os import listdir + from os import walk from os.path import join from vyos.utils.file import read_file scripts = [] dir = '/config/scripts' - for f in listdir(dir): - script = {} - path = join(dir, f) - data = read_file(path) - script["path"] = path - script["data"] = data - - scripts.append(script) + for dirpath, _, filenames in walk(dir): + for filename in filenames: + script = {} + path = join(dirpath, filename) + data = read_file(path) + script["path"] = path + script["data"] = data + + scripts.append(script) return scripts diff --git a/src/op_mode/zone.py b/src/op_mode/zone.py index 49fecdf28..df39549d2 100644 --- a/src/op_mode/zone.py +++ b/src/op_mode/zone.py @@ -56,10 +56,15 @@ def _convert_one_zone_data(zone: str, zone_config: dict) -> dict: from_zone_dict['firewall_v6'] = dict_search( 'firewall.ipv6_name', from_zone_config) list_of_rules.append(from_zone_dict) + zone_members =[] + interface_members = dict_search('member.interface', zone_config) + vrf_members = dict_search('member.vrf', zone_config) + zone_members += interface_members if interface_members is not None else [] + zone_members += vrf_members if vrf_members is not None else [] zone_dict = { 'name': zone, - 'interface': dict_search('interface', zone_config), + 'members': zone_members, 'type': 'LOCAL' if dict_search('local_zone', zone_config) is not None else None, } @@ -126,7 +131,7 @@ def output_zone_list(zone_conf: dict) -> list: if zone_conf['type'] == 'LOCAL': zone_info.append('LOCAL') else: - zone_info.append("\n".join(zone_conf['interface'])) + zone_info.append("\n".join(zone_conf['members'])) from_zone = [] firewall = [] @@ -175,7 +180,7 @@ def get_formatted_output(zone_policy: list) -> str: :rtype: str """ headers = ["Zone", - "Interfaces", + "Members", "From Zone", "Firewall IPv4", "Firewall IPv6" diff --git a/src/services/vyos-configd b/src/services/vyos-configd index d558e8c26..b161fe6ba 100755 --- a/src/services/vyos-configd +++ b/src/services/vyos-configd @@ -211,9 +211,6 @@ def initialization(socket): scripts_called = [] setattr(config, 'scripts_called', scripts_called) - if not hasattr(config, 'frrender_cls'): - setattr(config, 'frrender_cls', FRRender()) - return config @@ -312,8 +309,10 @@ if __name__ == '__main__': remove_if_file(configd_env_file) os.symlink(configd_env_set_file, configd_env_file) - config = None + # We only need one long-lived instance of FRRender + frr = FRRender() + config = None while True: # Wait for next request from client msg = socket.recv().decode() @@ -332,10 +331,11 @@ if __name__ == '__main__': scripts_called = getattr(config, 'scripts_called', []) logger.debug(f'scripts_called: {scripts_called}') - if hasattr(config, 'frrender_cls') and res == R_SUCCESS: - frrender_cls = getattr(config, 'frrender_cls') + if res == R_SUCCESS: tmp = get_frrender_dict(config) - frrender_cls.generate(tmp) - frrender_cls.apply() + if frr.generate(tmp): + # only apply a new FRR configuration if anything changed + # in comparison to the previous applied configuration + frr.apply() else: logger.critical(f'Unexpected message: {message}') diff --git a/src/helpers/vyos-domain-resolver.py b/src/services/vyos-domain-resolver index f5a1d9297..bc74a05d1 100755 --- a/src/helpers/vyos-domain-resolver.py +++ b/src/services/vyos-domain-resolver @@ -16,6 +16,7 @@ import json import time +import logging from vyos.configdict import dict_merge from vyos.configquery import ConfigTreeQuery @@ -48,6 +49,11 @@ ipv6_tables = { 'ip6 raw' } +logger = logging.getLogger(__name__) +logs_handler = logging.StreamHandler() +logger.addHandler(logs_handler) +logger.setLevel(logging.INFO) + def get_config(conf, node): node_config = conf.get_config_dict(node, key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) @@ -163,15 +169,15 @@ def update_fqdn(config, node): nft_conf_str = "\n".join(conf_lines) + "\n" code = run(f'nft --file -', input=nft_conf_str) - print(f'Updated {count} sets in {node} - result: {code}') + logger.info(f'Updated {count} sets in {node} - result: {code}') if __name__ == '__main__': - print(f'VyOS domain resolver') + logger.info(f'VyOS domain resolver') count = 1 while commit_in_progress(): if ( count % 60 == 0 ): - print(f'Commit still in progress after {count}s - waiting') + logger.info(f'Commit still in progress after {count}s - waiting') count += 1 time.sleep(1) @@ -179,7 +185,7 @@ if __name__ == '__main__': firewall = get_config(conf, base_firewall) nat = get_config(conf, base_nat) - print(f'interval: {timeout}s - cache: {cache}') + logger.info(f'interval: {timeout}s - cache: {cache}') while True: update_fqdn(firewall, 'firewall') diff --git a/src/systemd/vyos-domain-resolver.service b/src/systemd/vyos-domain-resolver.service index e63ae5e34..87a4748f4 100644 --- a/src/systemd/vyos-domain-resolver.service +++ b/src/systemd/vyos-domain-resolver.service @@ -6,7 +6,9 @@ ConditionPathExistsGlob=/run/use-vyos-domain-resolver* [Service] Type=simple Restart=always -ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/vyos-domain-resolver.py +ExecStart=/usr/bin/python3 -u /usr/libexec/vyos/services/vyos-domain-resolver +SyslogIdentifier=vyos-domain-resolver +SyslogFacility=daemon StandardError=journal StandardOutput=journal |