summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/labeler.yml12
-rw-r--r--.github/reviewers.yml3
-rw-r--r--.github/workflows/add-pr-labels.yml19
-rw-r--r--.github/workflows/auto-author-assign.yml21
-rw-r--r--.github/workflows/chceck-pr-message.yml18
-rw-r--r--.github/workflows/check-pr-conflicts.yml14
-rw-r--r--.github/workflows/check-stale.yml13
-rw-r--r--.github/workflows/check-unused-imports.yml15
-rw-r--r--.github/workflows/codeql.yml76
-rw-r--r--.github/workflows/label-backport.yml12
-rw-r--r--.github/workflows/linit-j2.yml18
-rw-r--r--.github/workflows/mergifyio_backport.yml22
-rw-r--r--.github/workflows/pr-conflicts.yml18
-rw-r--r--.github/workflows/pull-request-labels.yml20
-rw-r--r--.github/workflows/pull-request-management.yml25
-rw-r--r--.github/workflows/pull-request-message-check.yml23
-rw-r--r--.github/workflows/stale.yml22
-rw-r--r--.github/workflows/unused-imports.yml22
-rw-r--r--.gitignore4
-rw-r--r--CODEOWNERS1
-rw-r--r--Pipfile18
-rw-r--r--Pipfile.lock549
-rw-r--r--data/config-mode-dependencies/vyos-1x.json3
-rw-r--r--data/configd-include.json4
-rw-r--r--data/op-mode-standardized.json3
-rw-r--r--data/templates/accel-ppp/config_chap_secrets_radius.j215
-rw-r--r--data/templates/accel-ppp/pppoe.config.j22
-rw-r--r--data/templates/accel-ppp/sstp.config.j23
-rw-r--r--data/templates/firewall/upnpd.conf.j2227
-rw-r--r--data/templates/frr/zebra.vrf.route-map.frr.j26
-rw-r--r--data/templates/grub/grub_compat.j24
-rw-r--r--data/templates/grub/grub_options.j26
-rw-r--r--data/templates/load-balancing/haproxy.cfg.j210
-rw-r--r--data/templates/wifi/hostapd.conf.j27
-rw-r--r--debian/control4
-rw-r--r--interface-definitions/container.xml.in8
-rw-r--r--interface-definitions/include/accel-ppp/radius-additions.xml.i7
-rw-r--r--interface-definitions/include/firewall/match-interface.xml.i2
-rw-r--r--interface-definitions/include/haproxy/http-response-headers.xml.i29
-rw-r--r--interface-definitions/include/interface/evpn-mh-uplink.xml.i8
-rw-r--r--interface-definitions/include/nat-translation-options.xml.i8
-rw-r--r--interface-definitions/include/radius-priority.xml.i14
-rw-r--r--interface-definitions/include/version/nat-version.xml.i2
-rw-r--r--interface-definitions/include/version/pppoe-server-version.xml.i2
-rw-r--r--interface-definitions/interfaces_bonding.xml.in19
-rw-r--r--interface-definitions/interfaces_ethernet.xml.in8
-rw-r--r--interface-definitions/interfaces_openvpn.xml.in2
-rw-r--r--interface-definitions/load-balancing_reverse-proxy.xml.in2
-rw-r--r--interface-definitions/nat.xml.in1
-rw-r--r--interface-definitions/nat_cgnat.xml.in1
-rw-r--r--interface-definitions/policy.xml.in4
-rw-r--r--interface-definitions/service_config-sync.xml.in4
-rw-r--r--interface-definitions/service_pppoe-server.xml.in8
-rw-r--r--interface-definitions/service_upnp.xml.in229
-rw-r--r--interface-definitions/system_login.xml.in11
-rw-r--r--interface-definitions/vpn_sstp.xml.in9
-rw-r--r--interface-definitions/vrf.xml.in15
-rw-r--r--op-mode-definitions/force-commit-archive.xml.in2
-rw-r--r--op-mode-definitions/include/vni-tagnode-all.xml.i5
-rw-r--r--op-mode-definitions/include/vni-tagnode.xml.i5
-rw-r--r--op-mode-definitions/nat.xml.in33
-rw-r--r--op-mode-definitions/ntp.xml.in16
-rw-r--r--op-mode-definitions/show-evpn.xml.in59
-rw-r--r--op-mode-definitions/show-interfaces-bonding.xml.in34
-rw-r--r--op-mode-definitions/show-log.xml.in50
-rw-r--r--op-mode-definitions/system-image.xml.in9
-rw-r--r--python/vyos/config_mgmt.py2
-rw-r--r--python/vyos/firewall.py49
-rw-r--r--python/vyos/ifconfig/bond.py38
-rw-r--r--python/vyos/nat.py6
-rw-r--r--python/vyos/qos/base.py16
-rw-r--r--python/vyos/qos/randomdetect.py34
-rw-r--r--python/vyos/system/compat.py10
-rw-r--r--python/vyos/system/image.py10
-rw-r--r--python/vyos/template.py21
-rw-r--r--python/vyos/tpm.py2
-rw-r--r--python/vyos/utils/assertion.py4
-rw-r--r--python/vyos/utils/io.py2
-rw-r--r--python/vyos/version.py12
-rw-r--r--python/vyos/xml_ref/__init__.py6
-rw-r--r--python/vyos/xml_ref/definition.py27
-rwxr-xr-xscripts/check-pr-title-and-commit-messages.py51
-rw-r--r--smoketest/config-tests/container-simple1
-rw-r--r--smoketest/config-tests/nat-basic85
-rw-r--r--smoketest/configs/container-simple1
-rw-r--r--smoketest/configs/nat-basic256
-rw-r--r--smoketest/scripts/cli/base_accel_ppp_test.py23
-rwxr-xr-xsmoketest/scripts/cli/test_cgnat.py99
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py28
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_ethernet.py10
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireless.py11
-rwxr-xr-xsmoketest/scripts/cli/test_load-balancing_reverse-proxy.py21
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py60
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py21
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospf.py3
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py29
-rwxr-xr-xsmoketest/scripts/cli/test_service_pppoe-server.py9
-rwxr-xr-xsmoketest/scripts/cli/test_service_upnp.py103
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_l2tp.py23
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_sstp.py10
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py55
-rwxr-xr-xsmoketest/scripts/system/test_kernel_options.py76
-rwxr-xr-xsrc/completion/list_esi.sh20
-rwxr-xr-xsrc/completion/list_vni.sh20
-rwxr-xr-xsrc/conf_mode/container.py11
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py11
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py31
-rwxr-xr-xsrc/conf_mode/interfaces_ethernet.py17
-rwxr-xr-xsrc/conf_mode/load-balancing_reverse-proxy.py6
-rwxr-xr-xsrc/conf_mode/nat.py18
-rwxr-xr-xsrc/conf_mode/nat66.py22
-rwxr-xr-xsrc/conf_mode/nat_cgnat.py121
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py48
-rwxr-xr-xsrc/conf_mode/qos.py29
-rwxr-xr-xsrc/conf_mode/service_dhcpv6-server.py8
-rwxr-xr-xsrc/conf_mode/service_pppoe-server.py17
-rwxr-xr-xsrc/conf_mode/service_upnp.py157
-rwxr-xr-xsrc/conf_mode/system_host-name.py10
-rwxr-xr-xsrc/conf_mode/vrf.py5
-rw-r--r--src/conf_mode/vrf_vni.py103
-rwxr-xr-xsrc/helpers/vyos_config_sync.py23
-rwxr-xr-xsrc/migration-scripts/nat/7-to-862
-rwxr-xr-xsrc/migration-scripts/pppoe-server/9-to-1056
-rwxr-xr-xsrc/op_mode/bonding.py103
-rwxr-xr-xsrc/op_mode/cgnat.py96
-rw-r--r--src/op_mode/evpn.py46
-rwxr-xr-xsrc/op_mode/firewall.py12
-rwxr-xr-xsrc/op_mode/image_installer.py24
-rwxr-xr-xsrc/op_mode/image_manager.py28
-rwxr-xr-xsrc/op_mode/ipoe-control.py6
-rwxr-xr-xsrc/op_mode/nat.py2
-rw-r--r--src/op_mode/ntp.py164
-rwxr-xr-xsrc/op_mode/version.py6
-rw-r--r--src/systemd/miniupnpd.service13
134 files changed, 2307 insertions, 2047 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml
deleted file mode 100644
index e0b9ee430..000000000
--- a/.github/labeler.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-equuleus:
- - any:
- - base-branch: 'equuleus'
-current:
- - any:
- - base-branch: 'current'
-crux:
- - any:
- - base-branch: 'crux'
-sagitta:
- - any:
- - base-branch: 'sagitta'
diff --git a/.github/reviewers.yml b/.github/reviewers.yml
deleted file mode 100644
index a1647d20d..000000000
--- a/.github/reviewers.yml
+++ /dev/null
@@ -1,3 +0,0 @@
----
-"**/*":
- - team: reviewers
diff --git a/.github/workflows/add-pr-labels.yml b/.github/workflows/add-pr-labels.yml
new file mode 100644
index 000000000..1723cceb0
--- /dev/null
+++ b/.github/workflows/add-pr-labels.yml
@@ -0,0 +1,19 @@
+---
+name: Add pull request labels
+
+on:
+ pull_request_target:
+ branches:
+ - current
+ - crux
+ - equuleus
+ - sagitta
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ add-pr-label:
+ uses: vyos/.github/.github/workflows/add-pr-labels.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/auto-author-assign.yml b/.github/workflows/auto-author-assign.yml
index 1a7f8ef0b..c3696ea47 100644
--- a/.github/workflows/auto-author-assign.yml
+++ b/.github/workflows/auto-author-assign.yml
@@ -3,25 +3,12 @@ on:
pull_request_target:
types: [opened, reopened, ready_for_review, locked]
+
permissions:
pull-requests: write
+ contents: read
jobs:
- # https://github.com/marketplace/actions/auto-author-assign
assign-author:
- runs-on: ubuntu-latest
- steps:
- - name: "Assign Author to PR"
- uses: toshimaru/auto-author-assign@v1.6.2
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- # https://github.com/shufo/auto-assign-reviewer-by-files
- assign_reviewer:
- runs-on: ubuntu-latest
- steps:
- - name: Request review based on files changes and/or groups the author belongs to
- uses: shufo/auto-assign-reviewer-by-files@v1.1.4
- with:
- token: ${{ secrets.PR_ACTION_ASSIGN_REVIEWERS }}
- config: .github/reviewers.yml
+ uses: vyos/.github/.github/workflows/assign-author.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/chceck-pr-message.yml b/.github/workflows/chceck-pr-message.yml
new file mode 100644
index 000000000..e7e456961
--- /dev/null
+++ b/.github/workflows/chceck-pr-message.yml
@@ -0,0 +1,18 @@
+---
+name: Check pull request message format
+
+on:
+ pull_request:
+ branches:
+ - current
+ - crux
+ - equuleus
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ check-pr-title:
+ uses: vyos/.github/.github/workflows/check-pr-message.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/check-pr-conflicts.yml b/.github/workflows/check-pr-conflicts.yml
new file mode 100644
index 000000000..0c659e6ed
--- /dev/null
+++ b/.github/workflows/check-pr-conflicts.yml
@@ -0,0 +1,14 @@
+
+name: "PR Conflicts checker"
+on:
+ pull_request_target:
+ types: [synchronize]
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ check-pr-conflict-call:
+ uses: vyos/.github/.github/workflows/check-pr-merge-conflict.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/check-stale.yml b/.github/workflows/check-stale.yml
new file mode 100644
index 000000000..b5ec533f1
--- /dev/null
+++ b/.github/workflows/check-stale.yml
@@ -0,0 +1,13 @@
+name: "Issue and PR stale management"
+on:
+ schedule:
+ - cron: "0 0 * * *"
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ stale:
+ uses: vyos/.github/.github/workflows/check-stale.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/check-unused-imports.yml b/.github/workflows/check-unused-imports.yml
new file mode 100644
index 000000000..aada264f7
--- /dev/null
+++ b/.github/workflows/check-unused-imports.yml
@@ -0,0 +1,15 @@
+name: Check for unused imports using Pylint
+on:
+ pull_request:
+ branches:
+ - current
+ - sagitta
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ check-unused-imports:
+ uses: vyos/.github/.github/workflows/check-unused-imports.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index c39800ac8..f6472784d 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,74 +1,22 @@
-# For most projects, this workflow file will not need changing; you simply need
-# to commit it to your repository.
-#
-# You may wish to alter this file to override the set of languages analyzed,
-# or to provide custom queries or build logic.
-#
-# ******** NOTE ********
-# We have attempted to detect the languages in your repository. Please check
-# the `language` matrix defined below to confirm you have the correct set of
-# supported CodeQL languages.
-#
-name: "CodeQL"
+name: "Perform CodeQL Analysis"
on:
push:
- branches: [ "current", crux, equuleus ]
+ branches: [ "current", "sagitta", "equuleus" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "current" ]
schedule:
- cron: '22 10 * * 0'
-jobs:
- analyze:
- name: Analyze
- runs-on: ubuntu-latest
- permissions:
- actions: read
- contents: read
- security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ 'python' ]
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v3
-
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v2
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
-
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
- # queries: security-extended,security-and-quality
-
-
- # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v2
+permissions:
+ actions: read
+ contents: read
+ security-events: write
- # ℹ️ Command-line programs to run using the OS shell.
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
-
- # If the Autobuild fails above, remove it and uncomment the following three lines.
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
-
- # - run: |
- # echo "Run, Build Application using script"
- # ./location_of_script_within_repo/buildscript.sh
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
- with:
- category: "/language:${{matrix.language}}"
+jobs:
+ codeql-analysis-call:
+ uses: vyos/.github/.github/workflows/codeql-analysis.yml@feature/T6349-reusable-workflows
+ secrets: inherit
+ with:
+ languages: "['python']"
diff --git a/.github/workflows/label-backport.yml b/.github/workflows/label-backport.yml
new file mode 100644
index 000000000..9192b8184
--- /dev/null
+++ b/.github/workflows/label-backport.yml
@@ -0,0 +1,12 @@
+name: Mergifyio backport
+
+on: [issue_comment]
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ mergifyio-backport:
+ uses: vyos/.github/.github/workflows/label-backport.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/linit-j2.yml b/.github/workflows/linit-j2.yml
new file mode 100644
index 000000000..364a65a14
--- /dev/null
+++ b/.github/workflows/linit-j2.yml
@@ -0,0 +1,18 @@
+---
+name: J2 Lint
+
+on:
+ pull_request:
+ branches:
+ - current
+ - crux
+ - equuleus
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ j2lint:
+ uses: vyos/.github/.github/workflows/lint-j2.yml@feature/T6349-reusable-workflows
+ secrets: inherit
diff --git a/.github/workflows/mergifyio_backport.yml b/.github/workflows/mergifyio_backport.yml
deleted file mode 100644
index d9f863d9a..000000000
--- a/.github/workflows/mergifyio_backport.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Mergifyio backport
-
-on: [issue_comment]
-
-jobs:
- mergifyio_backport:
- if: github.repository == 'vyos/vyos-1x'
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - uses: actions-ecosystem/action-regex-match@v2
- id: regex-match
- with:
- text: ${{ github.event.comment.body }}
- regex: '@[Mm][Ee][Rr][Gg][Ii][Ff][Yy][Ii][Oo] backport '
-
- - uses: actions-ecosystem/action-add-labels@v1
- if: ${{ steps.regex-match.outputs.match != '' }}
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- labels: backport
diff --git a/.github/workflows/pr-conflicts.yml b/.github/workflows/pr-conflicts.yml
deleted file mode 100644
index 2fd0bb42d..000000000
--- a/.github/workflows/pr-conflicts.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: "PR Conflicts checker"
-on:
- pull_request_target:
- types: [synchronize]
-
-jobs:
- Conflict_Check:
- name: 'Check PR status: conflicts and resolution'
- runs-on: ubuntu-latest
- steps:
- - name: check if PRs are dirty
- uses: eps1lon/actions-label-merge-conflict@v3
- with:
- dirtyLabel: "state: conflict"
- removeOnDirtyLabel: "state: conflict resolved"
- repoToken: "${{ secrets.GITHUB_TOKEN }}"
- commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
- commentOnClean: "Conflicts have been resolved. A maintainer will review the pull request shortly."
diff --git a/.github/workflows/pull-request-labels.yml b/.github/workflows/pull-request-labels.yml
deleted file mode 100644
index 43856beaa..000000000
--- a/.github/workflows/pull-request-labels.yml
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Add pull request labels
-
-on:
- pull_request_target:
- branches:
- - current
- - crux
- - equuleus
- - sagitta
-
-jobs:
- add-pr-label:
- name: Add PR Labels
- runs-on: ubuntu-latest
- permissions:
- contents: read
- pull-requests: write
- steps:
- - uses: actions/labeler@v5
diff --git a/.github/workflows/pull-request-management.yml b/.github/workflows/pull-request-management.yml
deleted file mode 100644
index 3a855c107..000000000
--- a/.github/workflows/pull-request-management.yml
+++ /dev/null
@@ -1,25 +0,0 @@
----
-name: Build Pull Request Package
-
-on:
- pull_request:
- branches:
- - current
- - crux
- - equuleus
-
-jobs:
- j2lint:
- name: Validate j2 files
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- timeout-minutes: 2
- - name: Setup J2Lint
- timeout-minutes: 2
- run: |
- sudo pip install git+https://github.com/aristanetworks/j2lint.git@341b5d5db86e095b622f09770cb6367a1583620e
- - name: Run J2lint
- timeout-minutes: 2
- run: |
- j2lint $GITHUB_WORKSPACE/data
diff --git a/.github/workflows/pull-request-message-check.yml b/.github/workflows/pull-request-message-check.yml
deleted file mode 100644
index 8c206a5ab..000000000
--- a/.github/workflows/pull-request-message-check.yml
+++ /dev/null
@@ -1,23 +0,0 @@
----
-name: Check pull request message format
-
-on:
- pull_request:
- branches:
- - current
- - crux
- - equuleus
-
-jobs:
- check-pr-title:
- name: Check pull request title
- runs-on: ubuntu-20.04
- steps:
- - uses: actions/checkout@v2
- timeout-minutes: 2
- - name: Install the requests library
- run: pip3 install requests
- - name: Check the PR title
- timeout-minutes: 2
- run: |
- ./scripts/check-pr-title-and-commit-messages.py '${{ github.event.pull_request.url }}'
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
deleted file mode 100644
index d21d151f7..000000000
--- a/.github/workflows/stale.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: "Issue and PR stale management"
-on:
- schedule:
- - cron: "0 0 * * *"
-
-jobs:
- stale:
- runs-on: ubuntu-latest
- if: github.repository == 'vyos/vyos-1x'
- steps:
- # Issue stale management
- - uses: actions/stale@v6
- with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- days-before-stale: 90
- days-before-close: -1
- stale-issue-message: 'This issue is stale because it has been open 90 days with no activity. The issue will be reviewed by a maintainer and may be closed'
- stale-issue-label: 'state: stale'
- exempt-issue-labels: 'state: accepted, state: in-progress'
- stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. The PR will be reviewed by a maintainer and may be closed'
- stale-pr-label: 'state: stale'
- exempt-pr-labels: 'state: accepted, state: in-progress'
diff --git a/.github/workflows/unused-imports.yml b/.github/workflows/unused-imports.yml
deleted file mode 100644
index da57bd270..000000000
--- a/.github/workflows/unused-imports.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Check for unused imports using Pylint
-on:
- pull_request_target:
- branches:
- - current
- - sagitta
-
-jobs:
- Check-Unused-Imports:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v3
- with:
- python-version: 3.11
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install pylint
- - name: Analysing the code with pylint
- run: make unused-imports
diff --git a/.gitignore b/.gitignore
index 7707e94ca..507daceee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -145,3 +145,7 @@ data/component-versions.json
# vyos-1x XML cache
python/vyos/xml_ref/cache.py
python/vyos/xml_ref/pkg_cache/*_cache.py
+
+# We do not use pip
+Pipfile
+Pipfile.lock
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 000000000..191394298
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @vyos/reviewers \ No newline at end of file
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index 5a4fb6fe9..000000000
--- a/Pipfile
+++ /dev/null
@@ -1,18 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-lxml = "*"
-pylint = "*"
-nose = "*"
-coverage = "*"
-
-[packages]
-vyos = {file = "./python"}
-jinja2 = "*"
-paramiko = "*"
-
-[requires]
-python_version = "3.6"
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index 5a048becc..000000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,549 +0,0 @@
-{
- "_meta": {
- "hash": {
- "sha256": "2ec6c667a8fe2b96d82639414919deb0462744211709afe93f6a538a0b15dc1f"
- },
- "pipfile-spec": 6,
- "requires": {
- "python_version": "3.6"
- },
- "sources": [
- {
- "name": "pypi",
- "url": "https://pypi.org/simple",
- "verify_ssl": true
- }
- ]
- },
- "default": {
- "bcrypt": {
- "hashes": [
- "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f",
- "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5",
- "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb",
- "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258",
- "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4",
- "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc",
- "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2",
- "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326",
- "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483",
- "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a",
- "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966",
- "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63",
- "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c",
- "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551",
- "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d",
- "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e",
- "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0",
- "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c",
- "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb",
- "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1",
- "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42",
- "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946",
- "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab",
- "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1",
- "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c",
- "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7",
- "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==4.1.2"
- },
- "cffi": {
- "hashes": [
- "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc",
- "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a",
- "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417",
- "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab",
- "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520",
- "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36",
- "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743",
- "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8",
- "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed",
- "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684",
- "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56",
- "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324",
- "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d",
- "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235",
- "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e",
- "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088",
- "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000",
- "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7",
- "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e",
- "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673",
- "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c",
- "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe",
- "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2",
- "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098",
- "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8",
- "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a",
- "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0",
- "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b",
- "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896",
- "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e",
- "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9",
- "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2",
- "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b",
- "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6",
- "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404",
- "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f",
- "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0",
- "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4",
- "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc",
- "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936",
- "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba",
- "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872",
- "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb",
- "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614",
- "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1",
- "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d",
- "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969",
- "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b",
- "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4",
- "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627",
- "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956",
- "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"
- ],
- "markers": "platform_python_implementation != 'PyPy'",
- "version": "==1.16.0"
- },
- "cryptography": {
- "hashes": [
- "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee",
- "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576",
- "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d",
- "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30",
- "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413",
- "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb",
- "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da",
- "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4",
- "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd",
- "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc",
- "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8",
- "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1",
- "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc",
- "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e",
- "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8",
- "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940",
- "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400",
- "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7",
- "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16",
- "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278",
- "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74",
- "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec",
- "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1",
- "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2",
- "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c",
- "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922",
- "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a",
- "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6",
- "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1",
- "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e",
- "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac",
- "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==42.0.5"
- },
- "jinja2": {
- "hashes": [
- "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
- "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.7'",
- "version": "==3.1.3"
- },
- "markupsafe": {
- "hashes": [
- "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
- "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
- "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
- "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
- "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
- "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
- "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
- "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
- "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
- "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
- "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
- "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
- "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
- "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
- "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
- "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
- "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
- "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
- "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
- "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
- "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
- "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
- "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
- "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
- "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
- "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
- "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
- "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
- "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
- "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
- "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
- "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
- "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
- "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
- "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
- "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
- "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
- "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
- "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
- "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
- "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
- "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
- "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
- "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
- "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
- "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
- "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
- "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
- "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
- "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
- "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
- "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
- "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
- "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
- "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
- "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
- "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
- "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
- "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
- "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==2.1.5"
- },
- "paramiko": {
- "hashes": [
- "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7",
- "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.6'",
- "version": "==3.4.0"
- },
- "pycparser": {
- "hashes": [
- "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6",
- "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"
- ],
- "markers": "python_version >= '3.8'",
- "version": "==2.22"
- },
- "pynacl": {
- "hashes": [
- "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858",
- "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d",
- "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93",
- "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1",
- "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92",
- "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff",
- "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba",
- "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394",
- "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b",
- "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==1.5.0"
- },
- "vyos": {
- "file": "./python"
- }
- },
- "develop": {
- "astroid": {
- "hashes": [
- "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819",
- "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"
- ],
- "markers": "python_full_version >= '3.8.0'",
- "version": "==3.1.0"
- },
- "coverage": {
- "hashes": [
- "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c",
- "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63",
- "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7",
- "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f",
- "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8",
- "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf",
- "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0",
- "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384",
- "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76",
- "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7",
- "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d",
- "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70",
- "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f",
- "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818",
- "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b",
- "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d",
- "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec",
- "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083",
- "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2",
- "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9",
- "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd",
- "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade",
- "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e",
- "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a",
- "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227",
- "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87",
- "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c",
- "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e",
- "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c",
- "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e",
- "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd",
- "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec",
- "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562",
- "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8",
- "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677",
- "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357",
- "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c",
- "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd",
- "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49",
- "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286",
- "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1",
- "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf",
- "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51",
- "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409",
- "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384",
- "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e",
- "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978",
- "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57",
- "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e",
- "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2",
- "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48",
- "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.8'",
- "version": "==7.4.4"
- },
- "dill": {
- "hashes": [
- "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca",
- "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"
- ],
- "markers": "python_version >= '3.11'",
- "version": "==0.3.8"
- },
- "isort": {
- "hashes": [
- "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109",
- "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"
- ],
- "markers": "python_full_version >= '3.8.0'",
- "version": "==5.13.2"
- },
- "lxml": {
- "hashes": [
- "sha256:04ab5415bf6c86e0518d57240a96c4d1fcfc3cb370bb2ac2a732b67f579e5a04",
- "sha256:057cdc6b86ab732cf361f8b4d8af87cf195a1f6dc5b0ff3de2dced242c2015e0",
- "sha256:058a1308914f20784c9f4674036527e7c04f7be6fb60f5d61353545aa7fcb739",
- "sha256:08802f0c56ed150cc6885ae0788a321b73505d2263ee56dad84d200cab11c07a",
- "sha256:0a15438253b34e6362b2dc41475e7f80de76320f335e70c5528b7148cac253a1",
- "sha256:0c3f67e2aeda739d1cc0b1102c9a9129f7dc83901226cc24dd72ba275ced4218",
- "sha256:0e7259016bc4345a31af861fdce942b77c99049d6c2107ca07dc2bba2435c1d9",
- "sha256:0ed777c1e8c99b63037b91f9d73a6aad20fd035d77ac84afcc205225f8f41188",
- "sha256:0f5d65c39f16717a47c36c756af0fb36144069c4718824b7533f803ecdf91138",
- "sha256:0f8c09ed18ecb4ebf23e02b8e7a22a05d6411911e6fabef3a36e4f371f4f2585",
- "sha256:11a04306fcba10cd9637e669fd73aa274c1c09ca64af79c041aa820ea992b637",
- "sha256:1ae67b4e737cddc96c99461d2f75d218bdf7a0c3d3ad5604d1f5e7464a2f9ffe",
- "sha256:1c5bb205e9212d0ebddf946bc07e73fa245c864a5f90f341d11ce7b0b854475d",
- "sha256:1f7785f4f789fdb522729ae465adcaa099e2a3441519df750ebdccc481d961a1",
- "sha256:200e63525948e325d6a13a76ba2911f927ad399ef64f57898cf7c74e69b71095",
- "sha256:21c2e6b09565ba5b45ae161b438e033a86ad1736b8c838c766146eff8ceffff9",
- "sha256:2213afee476546a7f37c7a9b4ad4d74b1e112a6fafffc9185d6d21f043128c81",
- "sha256:27aa20d45c2e0b8cd05da6d4759649170e8dfc4f4e5ef33a34d06f2d79075d57",
- "sha256:2a66bf12fbd4666dd023b6f51223aed3d9f3b40fef06ce404cb75bafd3d89536",
- "sha256:2c9d147f754b1b0e723e6afb7ba1566ecb162fe4ea657f53d2139bbf894d050a",
- "sha256:2ddfe41ddc81f29a4c44c8ce239eda5ade4e7fc305fb7311759dd6229a080052",
- "sha256:31e9a882013c2f6bd2f2c974241bf4ba68c85eba943648ce88936d23209a2e01",
- "sha256:3249cc2989d9090eeac5467e50e9ec2d40704fea9ab72f36b034ea34ee65ca98",
- "sha256:3545039fa4779be2df51d6395e91a810f57122290864918b172d5dc7ca5bb433",
- "sha256:394ed3924d7a01b5bd9a0d9d946136e1c2f7b3dc337196d99e61740ed4bc6fe1",
- "sha256:3a6b45da02336895da82b9d472cd274b22dc27a5cea1d4b793874eead23dd14f",
- "sha256:3a74c4f27167cb95c1d4af1c0b59e88b7f3e0182138db2501c353555f7ec57f4",
- "sha256:3d0c3dd24bb4605439bf91068598d00c6370684f8de4a67c2992683f6c309d6b",
- "sha256:3dbe858ee582cbb2c6294dc85f55b5f19c918c2597855e950f34b660f1a5ede6",
- "sha256:3dc773b2861b37b41a6136e0b72a1a44689a9c4c101e0cddb6b854016acc0aa8",
- "sha256:3e183c6e3298a2ed5af9d7a356ea823bccaab4ec2349dc9ed83999fd289d14d5",
- "sha256:3f7765e69bbce0906a7c74d5fe46d2c7a7596147318dbc08e4a2431f3060e306",
- "sha256:417d14450f06d51f363e41cace6488519038f940676ce9664b34ebf5653433a5",
- "sha256:44f6c7caff88d988db017b9b0e4ab04934f11e3e72d478031efc7edcac6c622f",
- "sha256:491755202eb21a5e350dae00c6d9a17247769c64dcf62d8c788b5c135e179dc4",
- "sha256:4951e4f7a5680a2db62f7f4ab2f84617674d36d2d76a729b9a8be4b59b3659be",
- "sha256:52421b41ac99e9d91934e4d0d0fe7da9f02bfa7536bb4431b4c05c906c8c6919",
- "sha256:530e7c04f72002d2f334d5257c8a51bf409db0316feee7c87e4385043be136af",
- "sha256:533658f8fbf056b70e434dff7e7aa611bcacb33e01f75de7f821810e48d1bb66",
- "sha256:5670fb70a828663cc37552a2a85bf2ac38475572b0e9b91283dc09efb52c41d1",
- "sha256:56c22432809085b3f3ae04e6e7bdd36883d7258fcd90e53ba7b2e463efc7a6af",
- "sha256:58278b29cb89f3e43ff3e0c756abbd1518f3ee6adad9e35b51fb101c1c1daaec",
- "sha256:588008b8497667f1ddca7c99f2f85ce8511f8f7871b4a06ceede68ab62dff64b",
- "sha256:59565f10607c244bc4c05c0c5fa0c190c990996e0c719d05deec7030c2aa8289",
- "sha256:59689a75ba8d7ffca577aefd017d08d659d86ad4585ccc73e43edbfc7476781a",
- "sha256:5aea8212fb823e006b995c4dda533edcf98a893d941f173f6c9506126188860d",
- "sha256:5c670c0406bdc845b474b680b9a5456c561c65cf366f8db5a60154088c92d102",
- "sha256:5ca1e8188b26a819387b29c3895c47a5e618708fe6f787f3b1a471de2c4a94d9",
- "sha256:5d077bc40a1fe984e1a9931e801e42959a1e6598edc8a3223b061d30fbd26bbc",
- "sha256:5d5792e9b3fb8d16a19f46aa8208987cfeafe082363ee2745ea8b643d9cc5b45",
- "sha256:5dd1537e7cc06efd81371f5d1a992bd5ab156b2b4f88834ca852de4a8ea523fa",
- "sha256:5ea7b6766ac2dfe4bcac8b8595107665a18ef01f8c8343f00710b85096d1b53a",
- "sha256:622020d4521e22fb371e15f580d153134bfb68d6a429d1342a25f051ec72df1c",
- "sha256:627402ad8dea044dde2eccde4370560a2b750ef894c9578e1d4f8ffd54000461",
- "sha256:644df54d729ef810dcd0f7732e50e5ad1bd0a135278ed8d6bcb06f33b6b6f708",
- "sha256:64641a6068a16201366476731301441ce93457eb8452056f570133a6ceb15fca",
- "sha256:64c2baa7774bc22dd4474248ba16fe1a7f611c13ac6123408694d4cc93d66dbd",
- "sha256:6588c459c5627fefa30139be4d2e28a2c2a1d0d1c265aad2ba1935a7863a4913",
- "sha256:66bc5eb8a323ed9894f8fa0ee6cb3e3fb2403d99aee635078fd19a8bc7a5a5da",
- "sha256:68a2610dbe138fa8c5826b3f6d98a7cfc29707b850ddcc3e21910a6fe51f6ca0",
- "sha256:6935bbf153f9a965f1e07c2649c0849d29832487c52bb4a5c5066031d8b44fd5",
- "sha256:6992030d43b916407c9aa52e9673612ff39a575523c5f4cf72cdef75365709a5",
- "sha256:6a014510830df1475176466b6087fc0c08b47a36714823e58d8b8d7709132a96",
- "sha256:6ab833e4735a7e5533711a6ea2df26459b96f9eec36d23f74cafe03631647c41",
- "sha256:6cc6ee342fb7fa2471bd9b6d6fdfc78925a697bf5c2bcd0a302e98b0d35bfad3",
- "sha256:6cf58416653c5901e12624e4013708b6e11142956e7f35e7a83f1ab02f3fe456",
- "sha256:70a9768e1b9d79edca17890175ba915654ee1725975d69ab64813dd785a2bd5c",
- "sha256:70ac664a48aa64e5e635ae5566f5227f2ab7f66a3990d67566d9907edcbbf867",
- "sha256:71e97313406ccf55d32cc98a533ee05c61e15d11b99215b237346171c179c0b0",
- "sha256:7221d49259aa1e5a8f00d3d28b1e0b76031655ca74bb287123ef56c3db92f213",
- "sha256:74b28c6334cca4dd704e8004cba1955af0b778cf449142e581e404bd211fb619",
- "sha256:764b521b75701f60683500d8621841bec41a65eb739b8466000c6fdbc256c240",
- "sha256:78bfa756eab503673991bdcf464917ef7845a964903d3302c5f68417ecdc948c",
- "sha256:794f04eec78f1d0e35d9e0c36cbbb22e42d370dda1609fb03bcd7aeb458c6377",
- "sha256:79bd05260359170f78b181b59ce871673ed01ba048deef4bf49a36ab3e72e80b",
- "sha256:7a7efd5b6d3e30d81ec68ab8a88252d7c7c6f13aaa875009fe3097eb4e30b84c",
- "sha256:7c17b64b0a6ef4e5affae6a3724010a7a66bda48a62cfe0674dabd46642e8b54",
- "sha256:804f74efe22b6a227306dd890eecc4f8c59ff25ca35f1f14e7482bbce96ef10b",
- "sha256:853e074d4931dbcba7480d4dcab23d5c56bd9607f92825ab80ee2bd916edea53",
- "sha256:857500f88b17a6479202ff5fe5f580fc3404922cd02ab3716197adf1ef628029",
- "sha256:865bad62df277c04beed9478fe665b9ef63eb28fe026d5dedcb89b537d2e2ea6",
- "sha256:88e22fc0a6684337d25c994381ed8a1580a6f5ebebd5ad41f89f663ff4ec2885",
- "sha256:8b9c07e7a45bb64e21df4b6aa623cb8ba214dfb47d2027d90eac197329bb5e94",
- "sha256:8de8f9d6caa7f25b204fc861718815d41cbcf27ee8f028c89c882a0cf4ae4134",
- "sha256:8e77c69d5892cb5ba71703c4057091e31ccf534bd7f129307a4d084d90d014b8",
- "sha256:9123716666e25b7b71c4e1789ec829ed18663152008b58544d95b008ed9e21e9",
- "sha256:958244ad566c3ffc385f47dddde4145088a0ab893504b54b52c041987a8c1863",
- "sha256:96323338e6c14e958d775700ec8a88346014a85e5de73ac7967db0367582049b",
- "sha256:9676bfc686fa6a3fa10cd4ae6b76cae8be26eb5ec6811d2a325636c460da1806",
- "sha256:9b0ff53900566bc6325ecde9181d89afadc59c5ffa39bddf084aaedfe3b06a11",
- "sha256:9b9ec9c9978b708d488bec36b9e4c94d88fd12ccac3e62134a9d17ddba910ea9",
- "sha256:9c6ad0fbf105f6bcc9300c00010a2ffa44ea6f555df1a2ad95c88f5656104817",
- "sha256:9ca66b8e90daca431b7ca1408cae085d025326570e57749695d6a01454790e95",
- "sha256:9e2addd2d1866fe112bc6f80117bcc6bc25191c5ed1bfbcf9f1386a884252ae8",
- "sha256:a0af35bd8ebf84888373630f73f24e86bf016642fb8576fba49d3d6b560b7cbc",
- "sha256:a2b44bec7adf3e9305ce6cbfa47a4395667e744097faed97abb4728748ba7d47",
- "sha256:a2dfe7e2473f9b59496247aad6e23b405ddf2e12ef0765677b0081c02d6c2c0b",
- "sha256:a55ee573116ba208932e2d1a037cc4b10d2c1cb264ced2184d00b18ce585b2c0",
- "sha256:a7baf9ffc238e4bf401299f50e971a45bfcc10a785522541a6e3179c83eabf0a",
- "sha256:a8d5c70e04aac1eda5c829a26d1f75c6e5286c74743133d9f742cda8e53b9c2f",
- "sha256:a91481dbcddf1736c98a80b122afa0f7296eeb80b72344d7f45dc9f781551f56",
- "sha256:ab31a88a651039a07a3ae327d68ebdd8bc589b16938c09ef3f32a4b809dc96ef",
- "sha256:abc25c3cab9ec7fcd299b9bcb3b8d4a1231877e425c650fa1c7576c5107ab851",
- "sha256:adfb84ca6b87e06bc6b146dc7da7623395db1e31621c4785ad0658c5028b37d7",
- "sha256:afbbdb120d1e78d2ba8064a68058001b871154cc57787031b645c9142b937a62",
- "sha256:afd5562927cdef7c4f5550374acbc117fd4ecc05b5007bdfa57cc5355864e0a4",
- "sha256:b070bbe8d3f0f6147689bed981d19bbb33070225373338df755a46893528104a",
- "sha256:b0b58fbfa1bf7367dde8a557994e3b1637294be6cf2169810375caf8571a085c",
- "sha256:b560e3aa4b1d49e0e6c847d72665384db35b2f5d45f8e6a5c0072e0283430533",
- "sha256:b6241d4eee5f89453307c2f2bfa03b50362052ca0af1efecf9fef9a41a22bb4f",
- "sha256:b6787b643356111dfd4032b5bffe26d2f8331556ecb79e15dacb9275da02866e",
- "sha256:bcbf4af004f98793a95355980764b3d80d47117678118a44a80b721c9913436a",
- "sha256:beb72935a941965c52990f3a32d7f07ce869fe21c6af8b34bf6a277b33a345d3",
- "sha256:bf2e2458345d9bffb0d9ec16557d8858c9c88d2d11fed53998512504cd9df49b",
- "sha256:c2d35a1d047efd68027817b32ab1586c1169e60ca02c65d428ae815b593e65d4",
- "sha256:c38d7b9a690b090de999835f0443d8aa93ce5f2064035dfc48f27f02b4afc3d0",
- "sha256:c6f2c8372b98208ce609c9e1d707f6918cc118fea4e2c754c9f0812c04ca116d",
- "sha256:c817d420c60a5183953c783b0547d9eb43b7b344a2c46f69513d5952a78cddf3",
- "sha256:c8ba129e6d3b0136a0f50345b2cb3db53f6bda5dd8c7f5d83fbccba97fb5dcb5",
- "sha256:c94e75445b00319c1fad60f3c98b09cd63fe1134a8a953dcd48989ef42318534",
- "sha256:cc4691d60512798304acb9207987e7b2b7c44627ea88b9d77489bbe3e6cc3bd4",
- "sha256:cc518cea79fd1e2f6c90baafa28906d4309d24f3a63e801d855e7424c5b34144",
- "sha256:cd53553ddad4a9c2f1f022756ae64abe16da1feb497edf4d9f87f99ec7cf86bd",
- "sha256:cf22b41fdae514ee2f1691b6c3cdeae666d8b7fa9434de445f12bbeee0cf48dd",
- "sha256:d38c8f50ecf57f0463399569aa388b232cf1a2ffb8f0a9a5412d0db57e054860",
- "sha256:d3be9b2076112e51b323bdf6d5a7f8a798de55fb8d95fcb64bd179460cdc0704",
- "sha256:d4f2cc7060dc3646632d7f15fe68e2fa98f58e35dd5666cd525f3b35d3fed7f8",
- "sha256:d7520db34088c96cc0e0a3ad51a4fd5b401f279ee112aa2b7f8f976d8582606d",
- "sha256:d793bebb202a6000390a5390078e945bbb49855c29c7e4d56a85901326c3b5d9",
- "sha256:da052e7962ea2d5e5ef5bc0355d55007407087392cf465b7ad84ce5f3e25fe0f",
- "sha256:dae0ed02f6b075426accbf6b2863c3d0a7eacc1b41fb40f2251d931e50188dad",
- "sha256:ddc678fb4c7e30cf830a2b5a8d869538bc55b28d6c68544d09c7d0d8f17694dc",
- "sha256:df2e6f546c4df14bc81f9498bbc007fbb87669f1bb707c6138878c46b06f6510",
- "sha256:e02c5175f63effbd7c5e590399c118d5db6183bbfe8e0d118bdb5c2d1b48d937",
- "sha256:e196a4ff48310ba62e53a8e0f97ca2bca83cdd2fe2934d8b5cb0df0a841b193a",
- "sha256:e233db59c8f76630c512ab4a4daf5a5986da5c3d5b44b8e9fc742f2a24dbd460",
- "sha256:e32be23d538753a8adb6c85bd539f5fd3b15cb987404327c569dfc5fd8366e85",
- "sha256:e3d30321949861404323c50aebeb1943461a67cd51d4200ab02babc58bd06a86",
- "sha256:e89580a581bf478d8dcb97d9cd011d567768e8bc4095f8557b21c4d4c5fea7d0",
- "sha256:e998e304036198b4f6914e6a1e2b6f925208a20e2042563d9734881150c6c246",
- "sha256:ec42088248c596dbd61d4ae8a5b004f97a4d91a9fd286f632e42e60b706718d7",
- "sha256:efa7b51824aa0ee957ccd5a741c73e6851de55f40d807f08069eb4c5a26b2baa",
- "sha256:f0a1bc63a465b6d72569a9bba9f2ef0334c4e03958e043da1920299100bc7c08",
- "sha256:f18a5a84e16886898e51ab4b1d43acb3083c39b14c8caeb3589aabff0ee0b270",
- "sha256:f2a9efc53d5b714b8df2b4b3e992accf8ce5bbdfe544d74d5c6766c9e1146a3a",
- "sha256:f3bbbc998d42f8e561f347e798b85513ba4da324c2b3f9b7969e9c45b10f6169",
- "sha256:f42038016852ae51b4088b2862126535cc4fc85802bfe30dea3500fdfaf1864e",
- "sha256:f443cdef978430887ed55112b491f670bba6462cea7a7742ff8f14b7abb98d75",
- "sha256:f51969bac61441fd31f028d7b3b45962f3ecebf691a510495e5d2cd8c8092dbd",
- "sha256:f8aca2e3a72f37bfc7b14ba96d4056244001ddcc18382bd0daa087fd2e68a354",
- "sha256:f9737bf36262046213a28e789cc82d82c6ef19c85a0cf05e75c670a33342ac2c",
- "sha256:fd6037392f2d57793ab98d9e26798f44b8b4da2f2464388588f48ac52c489ea1",
- "sha256:feaa45c0eae424d3e90d78823f3828e7dc42a42f21ed420db98da2c4ecf0a2cb",
- "sha256:ff097ae562e637409b429a7ac958a20aab237a0378c42dabaa1e3abf2f896e5f",
- "sha256:ff46d772d5f6f73564979cd77a4fffe55c916a05f3cb70e7c9c0590059fb29ef"
- ],
- "index": "pypi",
- "markers": "python_version >= '3.6'",
- "version": "==5.2.1"
- },
- "mccabe": {
- "hashes": [
- "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
- "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
- ],
- "markers": "python_version >= '3.6'",
- "version": "==0.7.0"
- },
- "nose": {
- "hashes": [
- "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac",
- "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a",
- "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"
- ],
- "index": "pypi",
- "version": "==1.3.7"
- },
- "platformdirs": {
- "hashes": [
- "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068",
- "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"
- ],
- "markers": "python_version >= '3.8'",
- "version": "==4.2.0"
- },
- "pylint": {
- "hashes": [
- "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74",
- "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"
- ],
- "index": "pypi",
- "markers": "python_full_version >= '3.8.0'",
- "version": "==3.1.0"
- },
- "tomlkit": {
- "hashes": [
- "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b",
- "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"
- ],
- "markers": "python_version >= '3.7'",
- "version": "==0.12.4"
- }
- }
-}
diff --git a/data/config-mode-dependencies/vyos-1x.json b/data/config-mode-dependencies/vyos-1x.json
index afe3dd838..13de434bd 100644
--- a/data/config-mode-dependencies/vyos-1x.json
+++ b/data/config-mode-dependencies/vyos-1x.json
@@ -11,7 +11,8 @@
"ethernet": ["interfaces_ethernet"]
},
"interfaces_bridge": {
- "vxlan": ["interfaces_vxlan"]
+ "vxlan": ["interfaces_vxlan"],
+ "wlan": ["interfaces_wireless"]
},
"load_balancing_wan": {
"conntrack": ["system_conntrack"]
diff --git a/data/configd-include.json b/data/configd-include.json
index dc00f0698..dcee50306 100644
--- a/data/configd-include.json
+++ b/data/configd-include.json
@@ -81,7 +81,6 @@
"service_sla.py",
"service_ssh.py",
"service_tftp-server.py",
-"service_upnp.py",
"service_webproxy.py",
"system_acceleration.py",
"system_config-management.py",
@@ -108,6 +107,5 @@
"vpn_openconnect.py",
"vpn_pptp.py",
"vpn_sstp.py",
-"vrf.py",
-"vrf_vni.py"
+"vrf.py"
]
diff --git a/data/op-mode-standardized.json b/data/op-mode-standardized.json
index d3685caaf..c14133127 100644
--- a/data/op-mode-standardized.json
+++ b/data/op-mode-standardized.json
@@ -1,13 +1,16 @@
[
"accelppp.py",
"bgp.py",
+"bonding.py",
"bridge.py",
+"cgnat.py",
"config_mgmt.py",
"conntrack.py",
"container.py",
"cpu.py",
"dhcp.py",
"dns.py",
+"evpn.py",
"interfaces.py",
"ipsec.py",
"lldp.py",
diff --git a/data/templates/accel-ppp/config_chap_secrets_radius.j2 b/data/templates/accel-ppp/config_chap_secrets_radius.j2
index 595e3a565..e343ce461 100644
--- a/data/templates/accel-ppp/config_chap_secrets_radius.j2
+++ b/data/templates/accel-ppp/config_chap_secrets_radius.j2
@@ -5,7 +5,20 @@ chap-secrets={{ chap_secrets_file }}
[radius]
verbose=1
{% for server, options in authentication.radius.server.items() if not options.disable is vyos_defined %}
-server={{ server }},{{ options.key }},auth-port={{ options.port }},acct-port={{ options.acct_port }},req-limit=0,fail-time={{ options.fail_time }}
+{% set _server_cfg = "server=" %}
+{% set _server_cfg = _server_cfg + server %}
+{% set _server_cfg = _server_cfg + "," + options.key %}
+{% set _server_cfg = _server_cfg + ",auth-port=" + options.port %}
+{% set _server_cfg = _server_cfg + ",acct-port=" + options.acct_port %}
+{% set _server_cfg = _server_cfg + ",req-limit=0" %}
+{% set _server_cfg = _server_cfg + ",fail-time=" + options.fail_time %}
+{% if options.priority is vyos_defined %}
+{% set _server_cfg = _server_cfg + ",weight=" + options.priority %}
+{% endif %}
+{% if options.backup is vyos_defined %}
+{% set _server_cfg = _server_cfg + ",backup" %}
+{% endif %}
+{{ _server_cfg }}
{% endfor %}
{% if authentication.radius.accounting_interim_interval is vyos_defined %}
acct-interim-interval={{ authentication.radius.accounting_interim_interval }}
diff --git a/data/templates/accel-ppp/pppoe.config.j2 b/data/templates/accel-ppp/pppoe.config.j2
index ddf0da518..42bc8440c 100644
--- a/data/templates/accel-ppp/pppoe.config.j2
+++ b/data/templates/accel-ppp/pppoe.config.j2
@@ -67,7 +67,7 @@ service-name={{ service_name | join(',') }}
{% set delay_without_sessions = pado_delay.delays_without_sessions[0] | default('0') %}
{% set pado_delay_param = namespace(value=delay_without_sessions) %}
{% for delay, sessions in pado_delay.delays_with_sessions | sort(attribute='1') %}
-{% if not loop.last %}
+{% if not delay == 'disable' %}
{% set pado_delay_param.value = pado_delay_param.value + ',' + delay + ':' + sessions | string %}
{% else %}
{% set pado_delay_param.value = pado_delay_param.value + ',-1:' + sessions | string %}
diff --git a/data/templates/accel-ppp/sstp.config.j2 b/data/templates/accel-ppp/sstp.config.j2
index b624f83a3..22fb55506 100644
--- a/data/templates/accel-ppp/sstp.config.j2
+++ b/data/templates/accel-ppp/sstp.config.j2
@@ -42,6 +42,9 @@ accept=ssl
ssl-ca-file=/run/accel-pppd/sstp-ca.pem
ssl-pemfile=/run/accel-pppd/sstp-cert.pem
ssl-keyfile=/run/accel-pppd/sstp-cert.key
+{% if host_name is vyos_defined %}
+host-name={{ host_name }}
+{% endif %}
{% if default_pool is vyos_defined %}
ip-pool={{ default_pool }}
{% endif %}
diff --git a/data/templates/firewall/upnpd.conf.j2 b/data/templates/firewall/upnpd.conf.j2
deleted file mode 100644
index 616e8869f..000000000
--- a/data/templates/firewall/upnpd.conf.j2
+++ /dev/null
@@ -1,227 +0,0 @@
-# This is the UPNP configuration file
-
-# WAN network interface
-ext_ifname={{ wan_interface }}
-{% if wan_ip is vyos_defined %}
-
-# if the WAN network interface for IPv6 is different than for IPv4,
-# set ext_ifname6
-#ext_ifname6=eth2
-
-# If the WAN interface has several IP addresses, you
-# can specify the one to use below.
-# Setting ext_ip is also useful in double NAT setup, you can declare here
-# the public IP address.
-{% for addr in wan_ip %}
-ext_ip={{ addr }}
-{% endfor %}
-{% endif %}
-
-{% if stun is vyos_defined %}
-# WAN interface must have public IP address. Otherwise it is behind NAT
-# and port forwarding is impossible. In some cases WAN interface can be
-# behind unrestricted full-cone NAT 1:1 when all incoming traffic is NAT-ed and
-# routed to WAN interfaces without any filtering. In this cases miniupnpd
-# needs to know public IP address and it can be learnt by asking external
-# server via STUN protocol. Following option enable retrieving external
-# public IP address from STUN server and detection of NAT type. You need
-# to specify also external STUN server in stun_host option below.
-# This option is disabled by default.
-ext_perform_stun=yes
-# Specify STUN server, either hostname or IP address
-# Some public STUN servers:
-# stun.stunprotocol.org
-# stun.sipgate.net
-# stun.xten.com
-# stun.l.google.com (on non standard port 19302)
-ext_stun_host={{ stun.host }}
-# Specify STUN UDP port, by default it is standard port 3478.
-ext_stun_port={{ stun.port }}
-{% endif %}
-
-# LAN network interfaces IPs / networks
-{% if listen is vyos_defined %}
-# There can be multiple listening IPs for SSDP traffic, in that case
-# use multiple 'listening_ip=...' lines, one for each network interface.
-# It can be IP address or network interface name (ie. "eth0")
-# It is mandatory to use the network interface name in order to enable IPv6
-# HTTP is available on all interfaces.
-# When MULTIPLE_EXTERNAL_IP is enabled, the external IP
-# address associated with the subnet follows. For example:
-# listening_ip=192.168.0.1/24 88.22.44.13
-# When MULTIPLE_EXTERNAL_IP is disabled, you can list associated network
-# interfaces (for bridges)
-# listening_ip=bridge0 em0 wlan0
-{% for addr in listen %}
-{% if addr | is_ipv4 %}
-listening_ip={{ addr }}
-{% elif addr | is_ipv6 %}
-ipv6_listening_ip={{ addr }}
-{% else %}
-listening_ip={{ addr }}
-{% endif %}
-{% endfor %}
-{% endif %}
-
-# CAUTION: mixing up WAN and LAN interfaces may introduce security risks!
-# Be sure to assign the correct interfaces to LAN and WAN and consider
-# implementing UPnP permission rules at the bottom of this configuration file
-
-# Port for HTTP (descriptions and SOAP) traffic. Set to 0 for autoselect.
-#http_port=0
-# Port for HTTPS. Set to 0 for autoselect (default)
-#https_port=0
-
-# Path to the UNIX socket used to communicate with MiniSSDPd
-# If running, MiniSSDPd will manage M-SEARCH answering.
-# default is /var/run/minissdpd.sock
-#minissdpdsocket=/var/run/minissdpd.sock
-
-{% if nat_pmp is vyos_defined %}
-# Enable NAT-PMP support (default is no)
-enable_natpmp=yes
-{% endif %}
-
-# Enable UPNP support (default is yes)
-enable_upnp=yes
-
-{% if pcp_lifetime is vyos_defined %}
-# PCP
-# Configure the minimum and maximum lifetime of a port mapping in seconds
-# 120s and 86400s (24h) are suggested values from PCP-base
-{% if pcp_lifetime.max is vyos_defined %}
-max_lifetime={{ pcp_lifetime.max }}
-{% endif %}
-{% if pcp_lifetime.min is vyos_defined %}
-min_lifetime={{ pcp_lifetime.min }}
-{% endif %}
-{% endif %}
-
-# table names for netfilter nft. Default is "filter" for both
-#upnp_table_name=
-#upnp_nat_table_name=
-# chain names for netfilter and netfilter nft
-# netfilter : default are MINIUPNPD, MINIUPNPD, MINIUPNPD-POSTROUTING
-# netfilter nft : default are miniupnpd, prerouting_miniupnpd, postrouting_miniupnpd
-#upnp_forward_chain=forwardUPnP
-#upnp_nat_chain=UPnP
-#upnp_nat_postrouting_chain=UPnP-Postrouting
-
-# Lease file location
-lease_file=/config/upnp.leases
-
-# To enable the next few runtime options, see compile time
-# ENABLE_MANUFACTURER_INFO_CONFIGURATION (config.h)
-
-{% if friendly_name is vyos_defined %}
-# Name of this service, default is "`uname -s` router"
-friendly_name={{ friendly_name }}
-{% endif %}
-
-# Manufacturer name, default is "`uname -s`"
-manufacturer_name=VyOS
-
-# Manufacturer URL, default is URL of OS vendor
-manufacturer_url=https://vyos.io/
-
-# Model name, default is "`uname -s` router"
-model_name=VyOS Router Model
-
-# Model description, default is "`uname -s` router"
-model_description=Vyos open source enterprise router/firewall operating system
-
-# Model URL, default is URL of OS vendor
-model_url=https://vyos.io/
-
-# Bitrates reported by daemon in bits per second
-# by default miniupnpd tries to get WAN interface speed
-#bitrate_up=1000000
-#bitrate_down=10000000
-
-{% if secure_mode is vyos_defined %}
-# Secure Mode, UPnP clients can only add mappings to their own IP
-secure_mode=yes
-{% else %}
-# Secure Mode, UPnP clients can only add mappings to their own IP
-secure_mode=no
-{% endif %}
-
-{% if presentation_url is vyos_defined %}
-# Default presentation URL is HTTP address on port 80
-# If set to an empty string, no presentationURL element will appear
-# in the XML description of the device, which prevents MS Windows
-# from displaying an icon in the "Network Connections" panel.
-#presentation_url= {{ presentation_url }}
-{% endif %}
-
-# Report system uptime instead of daemon uptime
-system_uptime=yes
-
-# Notify interval in seconds. default is 30 seconds.
-#notify_interval=240
-notify_interval=60
-
-# Unused rules cleaning.
-# never remove any rule before this threshold for the number
-# of redirections is exceeded. default to 20
-clean_ruleset_threshold=10
-# Clean process work interval in seconds. default to 0 (disabled).
-# a 600 seconds (10 minutes) interval makes sense
-clean_ruleset_interval=600
-
-############################################################################
-## The next 5 config parameters (packet_log, anchor, queue, tag, quickrules)
-## are specific to BSD's pf(4) packet filter and hence cannot be enabled in
-## VyOS.
-# Log packets in pf (default is no)
-#packet_log=no
-
-# Anchor name in pf (default is miniupnpd)
-#anchor=miniupnpd
-
-# ALTQ queue in pf
-# Filter rules must be used for this to be used.
-# compile with PF_ENABLE_FILTER_RULES (see config.h file)
-#queue=queue_name1
-
-# Tag name in pf
-#tag=tag_name1
-
-# Make filter rules in pf quick or not. default is yes
-# active when compiled with PF_ENABLE_FILTER_RULES (see config.h file)
-#quickrules=no
-##
-## End of pf(4)-specific configuration not to be set in VyOS.
-############################################################################
-
-# UUID, generate your own UUID with "make genuuid"
-uuid={{ uuid }}
-
-# Daemon's serial and model number when reporting to clients
-# (in XML description)
-#serial=12345678
-#model_number=1
-
-# If compiled with IGD_V2 defined, force reporting IGDv1 in rootDesc (default
-# is no)
-#force_igd_desc_v1=no
-
-{% if rule is vyos_defined %}
-# UPnP permission rules (also enforced for NAT-PMP and PCP)
-# (allow|deny) (external port range) IP/mask (internal port range) (optional regex filter)
-# A port range is <min port>-<max port> or <port> if there is only
-# one port in the range.
-# IP/mask format must be nnn.nnn.nnn.nnn/nn
-# It is advised to only allow redirection of port >= 1024
-# and end the rule set with "deny 0-65535 0.0.0.0/0 0-65535"
-# The following default ruleset allows specific LAN side IP addresses
-# to request only ephemeral ports. It is recommended that users
-# modify the IP ranges to match their own internal networks, and
-# also consider implementing network-specific restrictions
-# CAUTION: failure to enforce any rules may permit insecure requests to be made!
-{% for rule, config in rule.items() %}
-{% if config.disable is not vyos_defined %}
-{{ config.action }} {{ config.external_port_range }} {{ config.ip }}{{ '/32' if '/' not in config.ip else '' }} {{ config.internal_port_range }}
-{% endif %}
-{% endfor %}
-{% endif %}
diff --git a/data/templates/frr/zebra.vrf.route-map.frr.j2 b/data/templates/frr/zebra.vrf.route-map.frr.j2
index f1cc6fe66..8ebb82511 100644
--- a/data/templates/frr/zebra.vrf.route-map.frr.j2
+++ b/data/templates/frr/zebra.vrf.route-map.frr.j2
@@ -1,10 +1,6 @@
!
{% if name is vyos_defined %}
{% for vrf, vrf_config in name.items() %}
-{# code path required for vrf_vni.py as we will only render the required VR configuration and not all of them #}
-{% if only_vrf is vyos_defined and vrf is not vyos_defined(only_vrf) %}
-{% continue %}
-{% endif %}
vrf {{ vrf }}
{% if vrf_config.ip.nht.no_resolve_via_default is vyos_defined %}
no ip nht resolve-via-default
@@ -25,7 +21,7 @@ vrf {{ vrf }}
ipv6 protocol {{ protocol_name }} route-map {{ protocol_config.route_map }}
{% endfor %}
{% endif %}
-{% if vrf_config.vni is vyos_defined and no_vni is not vyos_defined %}
+{% if vrf_config.vni is vyos_defined %}
vni {{ vrf_config.vni }}
{% endif %}
exit-vrf
diff --git a/data/templates/grub/grub_compat.j2 b/data/templates/grub/grub_compat.j2
index d1085eec8..8fb4f71dc 100644
--- a/data/templates/grub/grub_compat.j2
+++ b/data/templates/grub/grub_compat.j2
@@ -14,8 +14,6 @@
KVM
{%- elif type == 'ttyS' -%}
Serial
-{%- elif type == 'ttyUSB' -%}
- USB
{%- else -%}
Unknown
{%- endif %}
@@ -25,8 +23,6 @@
console=ttyS0,{{ console_speed }} console=tty0
{%- elif type == 'ttyS' -%}
console=tty0 console=ttyS0,{{ console_speed }}
-{%- elif type == 'ttyUSB' -%}
- console=tty0 console=ttyUSB0,115200
{%- else -%}
console=tty0 console=ttyS0,{{ console_speed }}
{%- endif %}
diff --git a/data/templates/grub/grub_options.j2 b/data/templates/grub/grub_options.j2
index c8a1472e1..a00bf4e37 100644
--- a/data/templates/grub/grub_options.j2
+++ b/data/templates/grub/grub_options.j2
@@ -33,12 +33,6 @@ submenu "Boot options" {
setup_serial
configfile ${prefix}/grub.cfg.d/*vyos-menu*.cfg
}
- menuentry "ttyUSB (USB serial)" {
- set console_type="ttyUSB"
- export console_type
- setup_serial
- configfile ${prefix}/grub.cfg.d/*vyos-menu*.cfg
- }
}
menuentry "Enter console number" {
read console_num
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2
index 7917c8257..797bf17e7 100644
--- a/data/templates/load-balancing/haproxy.cfg.j2
+++ b/data/templates/load-balancing/haproxy.cfg.j2
@@ -81,6 +81,11 @@ frontend {{ front }}
{% endif %}
{% endfor %}
{% endif %}
+{% if front_config.http_response_headers is vyos_defined %}
+{% for header, header_config in front_config.http_response_headers.items() %}
+ http-response set-header {{ header }} '{{ header_config['value'] }}'
+{% endfor %}
+{% endif %}
{% endif %}
{% if front_config.rule is vyos_defined %}
{% for rule, rule_config in front_config.rule.items() %}
@@ -158,6 +163,11 @@ backend {{ back }}
{% endif %}
{% if back_config.mode is vyos_defined %}
mode {{ back_config.mode }}
+{% if back_config.http_response_headers is vyos_defined %}
+{% for header, header_config in back_config.http_response_headers.items() %}
+ http-response set-header {{ header }} '{{ header_config['value'] }}'
+{% endfor %}
+{% endif %}
{% endif %}
{% if back_config.rule is vyos_defined %}
{% for rule, rule_config in back_config.rule.items() %}
diff --git a/data/templates/wifi/hostapd.conf.j2 b/data/templates/wifi/hostapd.conf.j2
index 83009242b..769325b49 100644
--- a/data/templates/wifi/hostapd.conf.j2
+++ b/data/templates/wifi/hostapd.conf.j2
@@ -28,6 +28,12 @@ interface={{ ifname }}
{% for bridge in is_bridge_member %}
bridge={{ bridge }}
{% endfor %}
+
+# WDS (4-address frame) mode with per-station virtual interfaces
+# (only supported with driver=nl80211)
+# This mode allows associated stations to use 4-address frames to allow layer 2
+# bridging to be used.
+wds_sta=1
{% endif %}
# Driver interface type (hostap/wired/none/nl80211/bsd);
@@ -739,4 +745,3 @@ wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0
-
diff --git a/debian/control b/debian/control
index 4151d83bb..2e99bdc28 100644
--- a/debian/control
+++ b/debian/control
@@ -10,6 +10,7 @@ Build-Depends:
iproute2,
libvyosconfig0 (>= 0.0.7),
libzmq3-dev,
+ procps,
python3 (>= 3.10),
# For QA
pylint,
@@ -197,9 +198,6 @@ Depends:
snmp,
snmpd,
# End "service snmp"
-# For "service upnp"
- miniupnpd-nftables,
-# End "service upnp"
# For "service webproxy"
squid,
squidclient,
diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in
index e7dacea36..2296a3e9e 100644
--- a/interface-definitions/container.xml.in
+++ b/interface-definitions/container.xml.in
@@ -15,9 +15,15 @@
<constraintErrorMessage>Container name must be alphanumeric and can contain hyphens</constraintErrorMessage>
</properties>
<children>
+ <leafNode name="allow-host-pid">
+ <properties>
+ <help>Allow sharing host process namespace with container</help>
+ <valueless/>
+ </properties>
+ </leafNode>
<leafNode name="allow-host-networks">
<properties>
- <help>Allow host networks in container</help>
+ <help>Allow sharing host networking with container</help>
<valueless/>
</properties>
</leafNode>
diff --git a/interface-definitions/include/accel-ppp/radius-additions.xml.i b/interface-definitions/include/accel-ppp/radius-additions.xml.i
index 3c2eb09eb..5222ba864 100644
--- a/interface-definitions/include/accel-ppp/radius-additions.xml.i
+++ b/interface-definitions/include/accel-ppp/radius-additions.xml.i
@@ -57,6 +57,13 @@
</properties>
<defaultValue>0</defaultValue>
</leafNode>
+ #include <include/radius-priority.xml.i>
+ <leafNode name="backup">
+ <properties>
+ <help>Use backup server if other servers are not available</help>
+ <valueless/>
+ </properties>
+ </leafNode>
</children>
</tagNode>
<leafNode name="timeout">
diff --git a/interface-definitions/include/firewall/match-interface.xml.i b/interface-definitions/include/firewall/match-interface.xml.i
index 5da6f51fb..f25686e72 100644
--- a/interface-definitions/include/firewall/match-interface.xml.i
+++ b/interface-definitions/include/firewall/match-interface.xml.i
@@ -19,7 +19,7 @@
<description>Inverted interface name to match</description>
</valueHelp>
<constraint>
- <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex>
+ <regex>(\!?)(bond|br|dum|en|ersp|eth|gnv|ifb|ipoe|lan|l2tp|l2tpeth|macsec|peth|ppp|pppoe|pptp|sstp|tun|veth|vti|vtun|vxlan|wg|wlan|wwan)([0-9]?)(\*?)(.+)?|(\!?)lo</regex>
<validator name="vrf-name"/>
</constraint>
</properties>
diff --git a/interface-definitions/include/haproxy/http-response-headers.xml.i b/interface-definitions/include/haproxy/http-response-headers.xml.i
new file mode 100644
index 000000000..9e7ddfd28
--- /dev/null
+++ b/interface-definitions/include/haproxy/http-response-headers.xml.i
@@ -0,0 +1,29 @@
+<!-- include start from haproxy/http-response-headers.xml.i -->
+<tagNode name="http-response-headers">
+ <properties>
+ <help>Headers to include in HTTP response</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>HTTP header name</description>
+ </valueHelp>
+ <constraint>
+ <regex>[-a-zA-Z]+</regex>
+ </constraint>
+ <constraintErrorMessage>Header names must only include alphabetical characters and hyphens</constraintErrorMessage>
+ </properties>
+ <children>
+ <leafNode name="value">
+ <properties>
+ <help>HTTP header value</help>
+ <valueHelp>
+ <format>txt</format>
+ <description>HTTP header value</description>
+ </valueHelp>
+ <constraint>
+ <regex>[[:ascii:]]{1,256}</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+</tagNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/evpn-mh-uplink.xml.i b/interface-definitions/include/interface/evpn-mh-uplink.xml.i
new file mode 100644
index 000000000..5f7fe1b7f
--- /dev/null
+++ b/interface-definitions/include/interface/evpn-mh-uplink.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/evpn-mh-uplink.xml.i -->
+<leafNode name="uplink">
+ <properties>
+ <help>Uplink to the VXLAN core</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i
index 6b95de045..c8900590f 100644
--- a/interface-definitions/include/nat-translation-options.xml.i
+++ b/interface-definitions/include/nat-translation-options.xml.i
@@ -28,22 +28,18 @@
<properties>
<help>Port mapping options</help>
<completionHelp>
- <list>random fully-random none</list>
+ <list>random none</list>
</completionHelp>
<valueHelp>
<format>random</format>
<description>Randomize source port mapping</description>
</valueHelp>
<valueHelp>
- <format>fully-random</format>
- <description>Full port randomization</description>
- </valueHelp>
- <valueHelp>
<format>none</format>
<description>Do not apply port randomization</description>
</valueHelp>
<constraint>
- <regex>(random|fully-random|none)</regex>
+ <regex>(random|none)</regex>
</constraint>
</properties>
<defaultValue>none</defaultValue>
diff --git a/interface-definitions/include/radius-priority.xml.i b/interface-definitions/include/radius-priority.xml.i
new file mode 100644
index 000000000..f77f5016e
--- /dev/null
+++ b/interface-definitions/include/radius-priority.xml.i
@@ -0,0 +1,14 @@
+<!-- include start from radius-priority.xml.i -->
+<leafNode name="priority">
+ <properties>
+ <help>Server priority</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Server priority</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/nat-version.xml.i b/interface-definitions/include/version/nat-version.xml.i
index 656da6e14..173e91ed3 100644
--- a/interface-definitions/include/version/nat-version.xml.i
+++ b/interface-definitions/include/version/nat-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/nat-version.xml.i -->
-<syntaxVersion component='nat' version='7'></syntaxVersion>
+<syntaxVersion component='nat' version='8'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/include/version/pppoe-server-version.xml.i b/interface-definitions/include/version/pppoe-server-version.xml.i
index c253c58d9..61de1277a 100644
--- a/interface-definitions/include/version/pppoe-server-version.xml.i
+++ b/interface-definitions/include/version/pppoe-server-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/pppoe-server-version.xml.i -->
-<syntaxVersion component='pppoe-server' version='9'></syntaxVersion>
+<syntaxVersion component='pppoe-server' version='10'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces_bonding.xml.in b/interface-definitions/interfaces_bonding.xml.in
index 92c0911db..cc0327f3d 100644
--- a/interface-definitions/interfaces_bonding.xml.in
+++ b/interface-definitions/interfaces_bonding.xml.in
@@ -102,12 +102,7 @@
</constraint>
</properties>
</leafNode>
- <leafNode name="uplink">
- <properties>
- <help>Uplink to the VXLAN core</help>
- <valueless/>
- </properties>
- </leafNode>
+ #include <include/interface/evpn-mh-uplink.xml.i>
</children>
</node>
<leafNode name="hash-policy">
@@ -176,6 +171,18 @@
</properties>
<defaultValue>0</defaultValue>
</leafNode>
+ <leafNode name="system-mac">
+ <properties>
+ <help>System MAC address for 802.3ad</help>
+ <valueHelp>
+ <format>macaddr</format>
+ <description>MAC address</description>
+ </valueHelp>
+ <constraint>
+ <validator name="mac-address"/>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name="lacp-rate">
<properties>
<help>Rate in which we will ask our link partner to transmit LACPDU packets</help>
diff --git a/interface-definitions/interfaces_ethernet.xml.in b/interface-definitions/interfaces_ethernet.xml.in
index 4e55bac7c..89f990d41 100644
--- a/interface-definitions/interfaces_ethernet.xml.in
+++ b/interface-definitions/interfaces_ethernet.xml.in
@@ -57,6 +57,14 @@
<defaultValue>auto</defaultValue>
</leafNode>
#include <include/interface/eapol.xml.i>
+ <node name="evpn">
+ <properties>
+ <help>EVPN Multihoming</help>
+ </properties>
+ <children>
+ #include <include/interface/evpn-mh-uplink.xml.i>
+ </children>
+ </node>
#include <include/interface/hw-id.xml.i>
#include <include/interface/ipv4-options.xml.i>
#include <include/interface/ipv6-options.xml.i>
diff --git a/interface-definitions/interfaces_openvpn.xml.in b/interface-definitions/interfaces_openvpn.xml.in
index 7b46f32b3..23cc83e9a 100644
--- a/interface-definitions/interfaces_openvpn.xml.in
+++ b/interface-definitions/interfaces_openvpn.xml.in
@@ -663,7 +663,7 @@
<help>Number of digits to use for totp hash</help>
<valueHelp>
<format>1-65535</format>
- <description>Seconds</description>
+ <description>Digits</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-65535"/>
diff --git a/interface-definitions/load-balancing_reverse-proxy.xml.in b/interface-definitions/load-balancing_reverse-proxy.xml.in
index 6a3b3cef1..011e1b53c 100644
--- a/interface-definitions/load-balancing_reverse-proxy.xml.in
+++ b/interface-definitions/load-balancing_reverse-proxy.xml.in
@@ -39,6 +39,7 @@
#include <include/port-number.xml.i>
#include <include/haproxy/rule-frontend.xml.i>
#include <include/haproxy/tcp-request.xml.i>
+ #include <include/haproxy/http-response-headers.xml.i>
<leafNode name="redirect-http-to-https">
<properties>
<help>Redirect HTTP to HTTPS</help>
@@ -90,6 +91,7 @@
</leafNode>
#include <include/generic-description.xml.i>
#include <include/haproxy/mode.xml.i>
+ #include <include/haproxy/http-response-headers.xml.i>
<node name="parameters">
<properties>
<help>Backend parameters</help>
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index 0a639bd80..73a748137 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -141,6 +141,7 @@
</children>
</node>
#include <include/inbound-interface.xml.i>
+ #include <include/firewall/log.xml.i>
<node name="translation">
<properties>
<help>Translation address or prefix</help>
diff --git a/interface-definitions/nat_cgnat.xml.in b/interface-definitions/nat_cgnat.xml.in
index caa26b4d9..fce5e655d 100644
--- a/interface-definitions/nat_cgnat.xml.in
+++ b/interface-definitions/nat_cgnat.xml.in
@@ -123,6 +123,7 @@
<validator name="ipv4-host"/>
<validator name="ipv4-range"/>
</constraint>
+ <multi/>
</properties>
</leafNode>
</children>
diff --git a/interface-definitions/policy.xml.in b/interface-definitions/policy.xml.in
index 791fa1d87..eb907cb9e 100644
--- a/interface-definitions/policy.xml.in
+++ b/interface-definitions/policy.xml.in
@@ -1546,11 +1546,11 @@
<properties>
<help>Set prefixes to table</help>
<valueHelp>
- <format>u32:1-200</format>
+ <format>u32:1-4294967295</format>
<description>Table value</description>
</valueHelp>
<constraint>
- <validator name="numeric" argument="--range 1-200"/>
+ <validator name="numeric" argument="--range 1-4294967295"/>
</constraint>
</properties>
</leafNode>
diff --git a/interface-definitions/service_config-sync.xml.in b/interface-definitions/service_config-sync.xml.in
index e9ea9aa4b..648c14aee 100644
--- a/interface-definitions/service_config-sync.xml.in
+++ b/interface-definitions/service_config-sync.xml.in
@@ -34,6 +34,10 @@
</constraint>
</properties>
</leafNode>
+ #include <include/port-number.xml.i>
+ <leafNode name="port">
+ <defaultValue>443</defaultValue>
+ </leafNode>
<leafNode name="timeout">
<properties>
<help>Connection API timeout</help>
diff --git a/interface-definitions/service_pppoe-server.xml.in b/interface-definitions/service_pppoe-server.xml.in
index 9b5e4d3fb..5d357c2f9 100644
--- a/interface-definitions/service_pppoe-server.xml.in
+++ b/interface-definitions/service_pppoe-server.xml.in
@@ -74,11 +74,19 @@
<properties>
<help>PADO delays</help>
<valueHelp>
+ <format>disable</format>
+ <description>Disable new connections</description>
+ </valueHelp>
+ <completionHelp>
+ <list>disable</list>
+ </completionHelp>
+ <valueHelp>
<format>u32:1-999999</format>
<description>Number in ms</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
+ <regex>disable</regex>
</constraint>
<constraintErrorMessage>Invalid PADO delay</constraintErrorMessage>
</properties>
diff --git a/interface-definitions/service_upnp.xml.in b/interface-definitions/service_upnp.xml.in
deleted file mode 100644
index 064386ee5..000000000
--- a/interface-definitions/service_upnp.xml.in
+++ /dev/null
@@ -1,229 +0,0 @@
-<?xml version="1.0"?>
-<interfaceDefinition>
- <node name="service">
- <children>
- <node name="upnp" owner="${vyos_conf_scripts_dir}/service_upnp.py">
- <properties>
- <help>Universal Plug and Play (UPnP) service</help>
- <priority>900</priority>
- </properties>
- <children>
- <leafNode name="friendly-name">
- <properties>
- <help>Name of this service</help>
- <valueHelp>
- <format>txt</format>
- <description>Friendly name</description>
- </valueHelp>
- </properties>
- </leafNode>
- <leafNode name="wan-interface">
- <properties>
- <help>WAN network interface</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <constraint>
- #include <include/constraint/interface-name.xml.i>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="wan-ip">
- <properties>
- <help>WAN network IP</help>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address" />
- <validator name="ipv6-address" />
- </constraint>
- <multi/>
- </properties>
- </leafNode>
- <leafNode name="nat-pmp">
- <properties>
- <help>Enable NAT-PMP support</help>
- <valueless />
- </properties>
- </leafNode>
- <leafNode name="secure-mode">
- <properties>
- <help>Enable Secure Mode</help>
- <valueless />
- </properties>
- </leafNode>
- <leafNode name="presentation-url">
- <properties>
- <help>Presentation Url</help>
- <valueHelp>
- <format>txt</format>
- <description>Presentation Url</description>
- </valueHelp>
- </properties>
- </leafNode>
- <node name="pcp-lifetime">
- <properties>
- <help>PCP-base lifetime Option</help>
- </properties>
- <children>
- <leafNode name="max">
- <properties>
- <help>Max lifetime time</help>
- <constraint>
- <validator name="numeric" />
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="min">
- <properties>
- <help>Min lifetime time</help>
- <constraint>
- <validator name="numeric" />
- </constraint>
- </properties>
- </leafNode>
- </children>
- </node>
- <leafNode name="listen">
- <properties>
- <help>Local IP addresses for service to listen on</help>
- <completionHelp>
- <script>${vyos_completion_dir}/list_local_ips.sh --both</script>
- <script>${vyos_completion_dir}/list_interfaces</script>
- </completionHelp>
- <valueHelp>
- <format>&lt;interface&gt;</format>
- <description>Monitor interface address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv4</format>
- <description>IPv4 address to listen for incoming connections</description>
- </valueHelp>
- <valueHelp>
- <format>ipv4net</format>
- <description>IPv4 prefix to listen for incoming connections</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>IPv6 address to listen for incoming connections</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6net</format>
- <description>IPv6 prefix to listen for incoming connections</description>
- </valueHelp>
- <multi/>
- <constraint>
- #include <include/constraint/interface-name.xml.i>
- <validator name="ip-address"/>
- <validator name="ipv4-prefix"/>
- <validator name="ipv6-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- <node name="stun">
- <properties>
- <help>Enable STUN probe support (can be used with NAT 1:1 support for WAN interfaces)</help>
- </properties>
- <children>
- <leafNode name="host">
- <properties>
- <help>The STUN server address</help>
- <valueHelp>
- <format>txt</format>
- <description>The STUN server host address</description>
- </valueHelp>
- <constraint>
- <validator name="fqdn"/>
- </constraint>
- </properties>
- </leafNode>
- #include <include/port-number.xml.i>
- </children>
- </node>
- <tagNode name="rule">
- <properties>
- <help>UPnP Rule</help>
- <valueHelp>
- <format>u32:0-65535</format>
- <description>Rule number</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-65535"/>
- </constraint>
- </properties>
- <children>
- #include <include/generic-disable-node.xml.i>
- <leafNode name="external-port-range">
- <properties>
- <help>Port range (REQUIRE)</help>
- <valueHelp>
- <format>&lt;port&gt;</format>
- <description>single port</description>
- </valueHelp>
- <valueHelp>
- <format>&lt;portN&gt;-&lt;portM&gt;</format>
- <description>Port range (use '-' as delimiter)</description>
- </valueHelp>
- <constraint>
- <validator name="port-range"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="internal-port-range">
- <properties>
- <help>Port range (REQUIRE)</help>
- <valueHelp>
- <format>&lt;port&gt;</format>
- <description>single port</description>
- </valueHelp>
- <valueHelp>
- <format>&lt;portN&gt;-&lt;portM&gt;</format>
- <description>Port range (use '-' as delimiter)</description>
- </valueHelp>
- <constraint>
- <validator name="port-range"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="ip">
- <properties>
- <help>The IP to which this rule applies (REQUIRE)</help>
- <valueHelp>
- <format>ipv4</format>
- <description>The IPv4 address to which this rule applies</description>
- </valueHelp>
- <valueHelp>
- <format>ipv4net</format>
- <description>The IPv4 to which this rule applies</description>
- </valueHelp>
- <constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv4-host"/>
- <validator name="ipv4-prefix"/>
- </constraint>
- </properties>
- </leafNode>
- <leafNode name="action">
- <properties>
- <help>Actions against the rule (REQUIRE)</help>
- <completionHelp>
- <list>allow deny</list>
- </completionHelp>
- <constraint>
- <regex>(allow|deny)</regex>
- </constraint>
- </properties>
- </leafNode>
- </children>
- </tagNode>
- </children>
- </node>
- </children>
- </node>
-</interfaceDefinition>
diff --git a/interface-definitions/system_login.xml.in b/interface-definitions/system_login.xml.in
index e94bb7219..f6c8021d3 100644
--- a/interface-definitions/system_login.xml.in
+++ b/interface-definitions/system_login.xml.in
@@ -202,17 +202,8 @@
<tagNode name="server">
<children>
#include <include/radius-timeout.xml.i>
+ #include <include/radius-priority.xml.i>
<leafNode name="priority">
- <properties>
- <help>Server priority</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Server priority</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
<defaultValue>255</defaultValue>
</leafNode>
</children>
diff --git a/interface-definitions/vpn_sstp.xml.in b/interface-definitions/vpn_sstp.xml.in
index d23a001d5..d9ed1c040 100644
--- a/interface-definitions/vpn_sstp.xml.in
+++ b/interface-definitions/vpn_sstp.xml.in
@@ -53,6 +53,15 @@
#include <include/accel-ppp/wins-server.xml.i>
#include <include/generic-description.xml.i>
#include <include/name-server-ipv4-ipv6.xml.i>
+ <leafNode name="host-name">
+ <properties>
+ <help>Only allow connection to specified host with the same TLS SNI</help>
+ <constraint>
+ #include <include/constraint/host-name.xml.i>
+ </constraint>
+ <constraintErrorMessage>Host-name must be alphanumeric and can contain hyphens</constraintErrorMessage>
+ </properties>
+ </leafNode>
</children>
</node>
</children>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index 94ed96e4b..a20be995a 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -120,20 +120,7 @@
<constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py $VAR(../@)">
- <properties>
- <help>Virtual Network Identifier</help>
- <!-- must be after BGP to keep correct order when removing L3VNIs in FRR -->
- <priority>822</priority>
- <valueHelp>
- <format>u32:0-16777214</format>
- <description>VXLAN virtual network identifier</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 0-16777214"/>
- </constraint>
- </properties>
- </leafNode>
+ #include <include/vni.xml.i>
</children>
</tagNode>
</children>
diff --git a/op-mode-definitions/force-commit-archive.xml.in b/op-mode-definitions/force-commit-archive.xml.in
index 162323c20..46836f967 100644
--- a/op-mode-definitions/force-commit-archive.xml.in
+++ b/op-mode-definitions/force-commit-archive.xml.in
@@ -6,7 +6,7 @@
<properties>
<help>Manually archive configuration</help>
</properties>
- <command>/usr/bin/config-mgmt</command>
+ <command>/etc/commit/post-hooks.d/02vyos-commit-archive; printf "\n"</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/include/vni-tagnode-all.xml.i b/op-mode-definitions/include/vni-tagnode-all.xml.i
index 0fedb9371..fabab19d7 100644
--- a/op-mode-definitions/include/vni-tagnode-all.xml.i
+++ b/op-mode-definitions/include/vni-tagnode-all.xml.i
@@ -3,9 +3,10 @@
<properties>
<help>VXLAN network identifier (VNI) number</help>
<completionHelp>
- <list>1-16777215 all</list>
+ <list>&lt;1-16777215&gt; all</list>
+ <script>${vyos_completion_dir}/list_vni.sh</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
</tagNode>
<!-- included end -->
diff --git a/op-mode-definitions/include/vni-tagnode.xml.i b/op-mode-definitions/include/vni-tagnode.xml.i
index 22f2d33bd..f5b99dcc8 100644
--- a/op-mode-definitions/include/vni-tagnode.xml.i
+++ b/op-mode-definitions/include/vni-tagnode.xml.i
@@ -3,9 +3,10 @@
<properties>
<help>VXLAN network identifier (VNI) number</help>
<completionHelp>
- <list>1-16777215</list>
+ <list>&lt;1-16777215&gt;</list>
+ <script>${vyos_completion_dir}/list_vni.sh</script>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
</tagNode>
<!-- included end -->
diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in
index 307a91337..13e7fd81d 100644
--- a/op-mode-definitions/nat.xml.in
+++ b/op-mode-definitions/nat.xml.in
@@ -7,6 +7,39 @@
<help>Show IPv4 Network Address Translation (NAT) information</help>
</properties>
<children>
+ <node name="cgnat">
+ <properties>
+ <help>Show Carrier-Grade Network Address Translation (CGNAT)</help>
+ </properties>
+ <children>
+ <node name="allocation">
+ <properties>
+ <help>Show allocated CGNAT parameters</help>
+ </properties>
+ <children>
+ <tagNode name="external-address">
+ <properties>
+ <help>Show CGNAT allocations for an external IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --external-address "$6"</command>
+ </tagNode>
+ <tagNode name="internal-address">
+ <properties>
+ <help>Show CGNAT allocations for an internal IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --internal-address "$6"</command>
+ </tagNode>
+ </children>
+ <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation</command>
+ </node>
+ </children>
+ </node>
<node name="source">
<properties>
<help>Show source IPv4 to IPv4 Network Address Translation (NAT) information</help>
diff --git a/op-mode-definitions/ntp.xml.in b/op-mode-definitions/ntp.xml.in
index b8d0c43ec..17250a45e 100644
--- a/op-mode-definitions/ntp.xml.in
+++ b/op-mode-definitions/ntp.xml.in
@@ -6,13 +6,25 @@
<properties>
<help>Show peer status of NTP daemon</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_ntp.sh --sourcestats</command>
+ <command>${vyos_op_scripts_dir}/ntp.py show_sourcestats</command>
<children>
+ <node name="activity">
+ <properties>
+ <help>Report the number of servers and peers that are online and offline</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/ntp.py show_activity</command>
+ </node>
+ <node name="sources">
+ <properties>
+ <help>Show information about the current time sources being accessed</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/ntp.py show_sources</command>
+ </node>
<node name="system">
<properties>
<help>Show parameters about the system clock performance</help>
</properties>
- <command>${vyos_op_scripts_dir}/show_ntp.sh --tracking</command>
+ <command>${vyos_op_scripts_dir}/ntp.py show_tracking</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-evpn.xml.in b/op-mode-definitions/show-evpn.xml.in
index a005cbc30..3c1e5c7d6 100644
--- a/op-mode-definitions/show-evpn.xml.in
+++ b/op-mode-definitions/show-evpn.xml.in
@@ -14,7 +14,7 @@
<children>
#include <include/frr-detail.xml.i>
</children>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
</node>
<tagNode name="access-vlan">
<properties>
@@ -31,7 +31,7 @@
<list>&lt;1-4094&gt;</list>
</completionHelp>
</properties>
- <command>${vyos_op_scripts_dir}/vtysh_wrapper.sh $@</command>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
</node>
</children>
</tagNode>
@@ -43,6 +43,45 @@
#include <include/vni-tagnode-all.xml.i>
</children>
</node>
+ <tagNode name="es">
+ <properties>
+ <help>Show ESI information for specified ESI</help>
+ <completionHelp>
+ <list>&lt;esi&gt;</list>
+ <script>${vyos_completion_dir}/list_esi.sh</script>
+ </completionHelp>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ </tagNode>
+ <node name="es">
+ <properties>
+ <help>Show ESI information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show ESI details</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ </leafNode>
+ </children>
+ </node>
+ <node name="es-evi">
+ <properties>
+ <help>Show ESI information per EVI</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show ESI per EVI details</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ </leafNode>
+ #include <include/vni-tagnode.xml.i>
+ </children>
+ </node>
<node name="mac">
<properties>
<help>MAC addresses</help>
@@ -67,7 +106,23 @@
#include <include/vni-tagnode-all.xml.i>
</children>
</node>
+ #include <include/vni-tagnode.xml.i>
+ <node name="vni">
+ <properties>
+ <help>Show VNI information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show VNI details</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
+ </leafNode>
+ </children>
+ </node>
</children>
+ <command>${vyos_op_scripts_dir}/evpn.py show_evpn --command "$*"</command>
</node>
</children>
</node>
diff --git a/op-mode-definitions/show-interfaces-bonding.xml.in b/op-mode-definitions/show-interfaces-bonding.xml.in
index 8ca5adb4f..e2950331b 100644
--- a/op-mode-definitions/show-interfaces-bonding.xml.in
+++ b/op-mode-definitions/show-interfaces-bonding.xml.in
@@ -23,8 +23,27 @@
<properties>
<help>Show detailed interface information</help>
</properties>
- <command>if [ -f "/proc/net/bonding/$4" ]; then cat "/proc/net/bonding/$4"; else echo "Interface $4 does not exist!"; fi</command>
+ <command>if [ -f "/proc/net/bonding/$4" ]; then sudo cat "/proc/net/bonding/$4"; else echo "Interface $4 does not exist!"; fi</command>
</leafNode>
+ <node name="lacp">
+ <properties>
+ <help>Show LACP related info</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show LACP details</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_detail --interface="$4" </command>
+ </leafNode>
+ <leafNode name="neighbors">
+ <properties>
+ <help>Show LACP Neighbors</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_neighbors --interface="$4"</command>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="slaves">
<properties>
<help>Show specified bonding interface information</help>
@@ -62,6 +81,19 @@
</properties>
<command>${vyos_op_scripts_dir}/interfaces.py show --intf-type=bonding</command>
</leafNode>
+ <node name="lacp">
+ <properties>
+ <help>Show LACP related info</help>
+ </properties>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show LACP details</help>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/bonding.py show_lacp_detail</command>
+ </leafNode>
+ </children>
+ </node>
<leafNode name="slaves">
<properties>
<help>Show specified bonding interface information</help>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index e13270364..c3aa324ba 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -464,12 +464,56 @@
</properties>
<command>journalctl --no-hostname --boot --unit lldpd.service</command>
</leafNode>
- <leafNode name="nat">
+ <node name="nat">
<properties>
<help>Show log for Network Address Translation (NAT)</help>
</properties>
- <command>egrep -i "kernel:.*\[NAT-[A-Z]{3,}-[0-9]+(-MASQ)?\]" $(find /var/log -maxdepth 1 -type f -name messages\* | sort -t. -k2nr)</command>
- </leafNode>
+ <children>
+ <node name="destination">
+ <properties>
+ <help>Show NAT destination log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-[0-9]+\]"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT destination log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-$6\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Show NAT source log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-[0-9]+(-MASQ)?\]"&quot;"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT source log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-$6(-MASQ)?\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="static">
+ <properties>
+ <help>Show NAT static log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-[0-9]+\]"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT static log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-$6\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ <command>journalctl --no-hostname --boot -k | egrep "\[(STATIC-)?(DST|SRC)-NAT-[0-9]+(-MASQ)?\]"</command>
+ </node>
<leafNode name="ndp-proxy">
<properties>
<help>Show log for Neighbor Discovery Protocol (NDP) Proxy</help>
diff --git a/op-mode-definitions/system-image.xml.in b/op-mode-definitions/system-image.xml.in
index 7b5260b4e..44b055be6 100644
--- a/op-mode-definitions/system-image.xml.in
+++ b/op-mode-definitions/system-image.xml.in
@@ -72,6 +72,15 @@
<help>Set system operational parameters</help>
</properties>
<children>
+ <tagNode name="boot-console">
+ <properties>
+ <help>Set system console type at boot</help>
+ <completionHelp>
+ <script>sudo ${vyos_op_scripts_dir}/image_manager.py --action list_console_types</script>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/image_manager.py --action set_console_type --console-type "${4}"</command>
+ </tagNode>
<node name="image">
<properties>
<help>Set system image parameters</help>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index fc51d781c..70b6ea203 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -283,6 +283,8 @@ Proceed ?'''
rollback_ct = self._get_config_tree_revision(rev)
try:
load(rollback_ct, switch='explicit')
+ print('Rollback diff has been applied.')
+ print('Use "compare" to review the changes or "commit" to apply them.')
except LoadConfigError as e:
raise ConfigMgmtError(e) from e
diff --git a/python/vyos/firewall.py b/python/vyos/firewall.py
index d9d605a9d..d7b7b80a8 100644
--- a/python/vyos/firewall.py
+++ b/python/vyos/firewall.py
@@ -32,7 +32,6 @@ from vyos.utils.process import cmd
from vyos.utils.process import run
# Conntrack
-
def conntrack_required(conf):
required_nodes = ['nat', 'nat66', 'load-balancing wan']
@@ -454,8 +453,28 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
else:
output.append(f'set update ip{def_suffix} saddr @DA{def_suffix}_{dyn_group}')
+ set_table = False
if 'set' in rule_conf:
- output.append(parse_policy_set(rule_conf['set'], def_suffix))
+ # Parse set command used in policy route:
+ if 'connection_mark' in rule_conf['set']:
+ conn_mark = rule_conf['set']['connection_mark']
+ output.append(f'ct mark set {conn_mark}')
+ if 'dscp' in rule_conf['set']:
+ dscp = rule_conf['set']['dscp']
+ output.append(f'ip{def_suffix} dscp set {dscp}')
+ if 'mark' in rule_conf['set']:
+ mark = rule_conf['set']['mark']
+ output.append(f'meta mark set {mark}')
+ if 'table' in rule_conf['set']:
+ set_table = True
+ table = rule_conf['set']['table']
+ if table == 'main':
+ table = '254'
+ mark = 0x7FFFFFFF - int(table)
+ output.append(f'meta mark set {mark}')
+ if 'tcp_mss' in rule_conf['set']:
+ mss = rule_conf['set']['tcp_mss']
+ output.append(f'tcp option maxseg size set {mss}')
if 'action' in rule_conf:
# Change action=return to action=action
@@ -488,6 +507,10 @@ def parse_rule(rule_conf, hook, fw_name, rule_id, ip_name):
if synproxy_ws:
output.append(f'wscale {synproxy_ws} timestamp sack-perm')
+ else:
+ if set_table:
+ output.append('return')
+
output.append(f'comment "{family}-{hook}-{fw_name}-{rule_id}"')
return " ".join(output)
@@ -518,28 +541,6 @@ def parse_time(time):
out.append(f'day {{{",".join(out_days)}}}')
return " ".join(out)
-def parse_policy_set(set_conf, def_suffix):
- out = []
- if 'connection_mark' in set_conf:
- conn_mark = set_conf['connection_mark']
- out.append(f'ct mark set {conn_mark}')
- if 'dscp' in set_conf:
- dscp = set_conf['dscp']
- out.append(f'ip{def_suffix} dscp set {dscp}')
- if 'mark' in set_conf:
- mark = set_conf['mark']
- out.append(f'meta mark set {mark}')
- if 'table' in set_conf:
- table = set_conf['table']
- if table == 'main':
- table = '254'
- mark = 0x7FFFFFFF - int(table)
- out.append(f'meta mark set {mark}')
- if 'tcp_mss' in set_conf:
- mss = set_conf['tcp_mss']
- out.append(f'tcp option maxseg size set {mss}')
- return " ".join(out)
-
# GeoIP
nftables_geoip_conf = '/run/nftables-geoip.conf'
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index c6d0f1cff..b8ea90049 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -18,6 +18,7 @@ import os
from vyos.ifconfig.interface import Interface
from vyos.utils.dict import dict_search
from vyos.utils.assertion import assert_list
+from vyos.utils.assertion import assert_mac
from vyos.utils.assertion import assert_positive
@Interface.register
@@ -54,6 +55,10 @@ class BondIf(Interface):
'validate': lambda v: assert_list(v, ['slow', 'fast']),
'location': '/sys/class/net/{ifname}/bonding/lacp_rate',
},
+ 'bond_system_mac': {
+ 'validate': lambda v: assert_mac(v, test_all_zero=False),
+ 'location': '/sys/class/net/{ifname}/bonding/ad_actor_system',
+ },
'bond_miimon': {
'validate': assert_positive,
'location': '/sys/class/net/{ifname}/bonding/miimon'
@@ -385,6 +390,24 @@ class BondIf(Interface):
"""
return self.set_interface('bond_mode', mode)
+ def set_system_mac(self, mac):
+ """
+ In an AD system, this specifies the mac-address for the actor in
+ protocol packet exchanges (LACPDUs). The value cannot be NULL or
+ multicast. It is preferred to have the local-admin bit set for this
+ mac but driver does not enforce it. If the value is not given then
+ system defaults to using the masters' mac address as actors' system
+ address.
+
+ This parameter has effect only in 802.3ad mode and is available through
+ SysFs interface.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').set_system_mac('00:50:ab:cd:ef:01')
+ """
+ return self.set_interface('bond_system_mac', mac)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -426,14 +449,13 @@ class BondIf(Interface):
Interface(interface).set_admin_state('up')
# Bonding policy/mode - default value, always present
- mode = config.get('mode')
- self.set_mode(mode)
+ self.set_mode(config['mode'])
# LACPDU transmission rate - default value
- if mode == '802.3ad':
+ if config['mode'] == '802.3ad':
self.set_lacp_rate(config.get('lacp_rate'))
- if mode not in ['802.3ad', 'balance-tlb', 'balance-alb']:
+ if config['mode'] not in ['802.3ad', 'balance-tlb', 'balance-alb']:
tmp = dict_search('arp_monitor.interval', config)
value = tmp if (tmp != None) else '0'
self.set_arp_interval(value)
@@ -468,6 +490,14 @@ class BondIf(Interface):
Interface(interface).flush_addrs()
self.add_port(interface)
+ # Add system mac address for 802.3ad - default address is all zero
+ # mode is always present (defaultValue)
+ if config['mode'] == '802.3ad':
+ mac = '00:00:00:00:00:00'
+ if 'system_mac' in config:
+ mac = config['system_mac']
+ self.set_system_mac(mac)
+
# Primary device interface - must be set after 'mode'
value = config.get('primary')
if value: self.set_primary(value)
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 2ada29add..e54548788 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -300,12 +300,12 @@ def parse_nat_static_rule(rule_conf, rule_id, nat_type):
output.append('counter')
- if translation_str:
- output.append(translation_str)
-
if 'log' in rule_conf:
output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
+ if translation_str:
+ output.append(translation_str)
+
output.append(f'comment "{log_prefix}"')
return " ".join(output)
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 4173a1a43..98e486e42 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -90,13 +90,14 @@ class QoSBase:
else:
return value
- def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None, mark_probability=None):
+ def _calc_random_detect_queue_params(self, avg_pkt, max_thr, limit=None, min_thr=None,
+ mark_probability=None, precedence=0):
params = dict()
avg_pkt = int(avg_pkt)
max_thr = int(max_thr)
mark_probability = int(mark_probability)
limit = int(limit) if limit else 4 * max_thr
- min_thr = int(min_thr) if min_thr else (9 * max_thr) // 18
+ min_thr = int(min_thr) if min_thr else ((9 + precedence) * max_thr) // 18
params['avg_pkt'] = avg_pkt
params['limit'] = limit * avg_pkt
@@ -246,9 +247,15 @@ class QoSBase:
filter_cmd_base += ' protocol all'
if 'match' in cls_config:
- is_filtered = False
+ has_filter = False
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
+ if not has_filter:
+ for key in ['mark', 'vif', 'ip', 'ipv6']:
+ if key in match_config:
+ has_filter = True
+ break
+
if self.qostype == 'shaper' and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
if 'mark' in match_config:
@@ -331,13 +338,12 @@ class QoSBase:
cls = int(cls)
filter_cmd += f' flowid {self._parent:x}:{cls:x}'
self._cmd(filter_cmd)
- is_filtered = True
vlan_expression = "match.*.vif"
match_vlan = jmespath.search(vlan_expression, cls_config)
if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) \
- and is_filtered:
+ and has_filter:
# For "vif" "basic match" is used instead of "action police" T5961
if not match_vlan:
filter_cmd += f' action police'
diff --git a/python/vyos/qos/randomdetect.py b/python/vyos/qos/randomdetect.py
index d7d84260f..a3a39da36 100644
--- a/python/vyos/qos/randomdetect.py
+++ b/python/vyos/qos/randomdetect.py
@@ -21,33 +21,25 @@ class RandomDetect(QoSBase):
# https://man7.org/linux/man-pages/man8/tc.8.html
def update(self, config, direction):
- tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 dsmark indices 8 set_tc_index'
+ # # Generalized Random Early Detection
+ handle = self._parent
+ tmp = f'tc qdisc add dev {self._interface} root handle {self._parent}:0 gred setup DPs 8 default 0 grio'
self._cmd(tmp)
-
- tmp = f'tc filter add dev {self._interface} parent {self._parent}:0 protocol ip prio 1 tcindex mask 0xe0 shift 5'
- self._cmd(tmp)
-
- # Generalized Random Early Detection
- handle = self._parent +1
- tmp = f'tc qdisc add dev {self._interface} parent {self._parent}:0 handle {handle}:0 gred setup DPs 8 default 0 grio'
- self._cmd(tmp)
-
bandwidth = self._rate_convert(config['bandwidth'])
# set VQ (virtual queue) parameters
for precedence, precedence_config in config['precedence'].items():
precedence = int(precedence)
- avg_pkt = int(precedence_config['average_packet'])
- limit = int(precedence_config['queue_limit']) * avg_pkt
- min_val = int(precedence_config['minimum_threshold']) * avg_pkt
- max_val = int(precedence_config['maximum_threshold']) * avg_pkt
-
- tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {limit} min {min_val} max {max_val} avpkt {avg_pkt} '
-
- burst = (2 * int(precedence_config['minimum_threshold']) + int(precedence_config['maximum_threshold'])) // 3
- probability = 1 / int(precedence_config['mark_probability'])
- tmp += f'burst {burst} bandwidth {bandwidth} probability {probability} DP {precedence} prio {8 - precedence:x}'
-
+ qparams = self._calc_random_detect_queue_params(
+ avg_pkt=precedence_config.get('average_packet'),
+ max_thr=precedence_config.get('maximum_threshold'),
+ limit=precedence_config.get('queue_limit'),
+ min_thr=precedence_config.get('minimum_threshold'),
+ mark_probability=precedence_config.get('mark_probability'),
+ precedence=precedence
+ )
+ tmp = f'tc qdisc change dev {self._interface} handle {handle}:0 gred limit {qparams["limit"]} min {qparams["min_val"]} max {qparams["max_val"]} avpkt {qparams["avg_pkt"]} '
+ tmp += f'burst {qparams["burst"]} bandwidth {bandwidth} probability {qparams["probability"]} DP {precedence} prio {8 - precedence:x}'
self._cmd(tmp)
# call base class
diff --git a/python/vyos/system/compat.py b/python/vyos/system/compat.py
index 1b487c1d2..d35bddea2 100644
--- a/python/vyos/system/compat.py
+++ b/python/vyos/system/compat.py
@@ -220,14 +220,8 @@ def get_default(data: dict, root_dir: str = '') -> Union[int, None]:
sublist = list(filter(lambda x: (x.get('version') == image_name and
x.get('console_type') == console_type and
- x.get('console_num') == console_num and
x.get('bootmode') == 'normal'),
menu_entries))
- # legacy images added with legacy tools omitted 'ttyUSB'; if entry not
- # available, default to initial entry of version
- if not sublist:
- sublist = list(filter(lambda x: x.get('version') == image_name,
- menu_entries))
if sublist:
return menu_entries.index(sublist[0])
@@ -253,6 +247,10 @@ def update_version_list(root_dir: str = '') -> list[dict]:
menu_entries = parse_menuentries(grub_cfg_main)
menu_versions = find_versions(menu_entries)
+ # remove deprecated console-type ttyUSB
+ menu_entries = list(filter(lambda x: x.get('console_type') != 'ttyUSB',
+ menu_entries))
+
# get list of versions added/removed by image-tools
current_versions = grub.version_list(root_dir)
diff --git a/python/vyos/system/image.py b/python/vyos/system/image.py
index ba9a6dfa7..aae52e770 100644
--- a/python/vyos/system/image.py
+++ b/python/vyos/system/image.py
@@ -18,8 +18,9 @@ from re import compile as re_compile
from functools import wraps
from tempfile import TemporaryDirectory
from typing import TypedDict
+from json import loads
-from vyos import version
+from vyos.defaults import directories
from vyos.system import disk, grub
# Define variables
@@ -201,9 +202,12 @@ def get_running_image() -> str:
if running_image_result:
running_image: str = running_image_result.groupdict().get(
'image_version', '')
- # we need to have a fallback for live systems
+ # we need to have a fallback for live systems:
+ # explicit read from version file
if not running_image:
- running_image: str = version.get_version()
+ json_data: str = Path(directories['data']).joinpath('version.json').read_text()
+ dict_data: dict = loads(json_data)
+ running_image: str = dict_data['version']
return running_image
diff --git a/python/vyos/template.py b/python/vyos/template.py
index ac77e8a3d..fbc5f1456 100644
--- a/python/vyos/template.py
+++ b/python/vyos/template.py
@@ -25,6 +25,14 @@ from vyos.utils.file import makedir
from vyos.utils.permission import chmod
from vyos.utils.permission import chown
+# We use a mutable global variable for the default template directory
+# to make it possible to call scripts from this repository
+# outside of live VyOS systems.
+# If something (like the image build scripts)
+# want to call a script, they can modify the default location
+# to the repository path.
+DEFAULT_TEMPLATE_DIR = directories["templates"]
+
# Holds template filters registered via register_filter()
_FILTERS = {}
_TESTS = {}
@@ -35,18 +43,7 @@ def _get_environment(location=None):
from os import getenv
if location is None:
- # Sometimes functions that rely on templates need to be executed outside of VyOS installations:
- # for example, installer functions are executed for image builds,
- # and anything may be invoked for testing from a developer's machine.
- # This environment variable allows running any unmodified code
- # with a custom template location.
- location_env_var = getenv("VYOS_TEMPLATE_DIR")
- if location_env_var:
- print(f"Using environment variable {location_env_var}")
- template_dir = location_env_var
- else:
- template_dir = directories["templates"]
- loc_loader=FileSystemLoader(template_dir)
+ loc_loader=FileSystemLoader(DEFAULT_TEMPLATE_DIR)
else:
loc_loader=FileSystemLoader(location)
env = Environment(
diff --git a/python/vyos/tpm.py b/python/vyos/tpm.py
index b9f28546f..a24f149fd 100644
--- a/python/vyos/tpm.py
+++ b/python/vyos/tpm.py
@@ -15,7 +15,7 @@
import os
import tempfile
-from vyos.util import rc_cmd
+from vyos.utils.process import rc_cmd
default_pcrs = ['0','2','4','7']
tpm_handle = 0x81000000
diff --git a/python/vyos/utils/assertion.py b/python/vyos/utils/assertion.py
index 1aaa54dff..c7fa220c3 100644
--- a/python/vyos/utils/assertion.py
+++ b/python/vyos/utils/assertion.py
@@ -53,7 +53,7 @@ def assert_mtu(mtu, ifname):
if (max_mtu and cur_mtu > max_mtu) or cur_mtu > 65536:
raise ValueError(f'MTU is too small for interface "{ifname}": {mtu} > {max_mtu}')
-def assert_mac(m):
+def assert_mac(m, test_all_zero=True):
split = m.split(':')
size = len(split)
@@ -74,7 +74,7 @@ def assert_mac(m):
raise ValueError(f'{m} is a multicast MAC address')
# overall mac address is not allowed to be 00:00:00:00:00:00
- if sum(octets) == 0:
+ if test_all_zero and sum(octets) == 0:
raise ValueError('00:00:00:00:00:00 is not a valid MAC address')
if octets[:5] == (0, 0, 94, 0, 1):
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index a8c430f28..205210b66 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -72,6 +72,8 @@ def ask_yes_no(question, default=False) -> bool:
stdout.write("Please respond with yes/y or no/n\n")
except EOFError:
stdout.write("\nPlease respond with yes/y or no/n\n")
+ except KeyboardInterrupt:
+ return False
def is_interactive():
"""Try to determine if the routine was called from an interactive shell."""
diff --git a/python/vyos/version.py b/python/vyos/version.py
index b5ed2705b..86e96d0ec 100644
--- a/python/vyos/version.py
+++ b/python/vyos/version.py
@@ -33,11 +33,11 @@ import os
import requests
import vyos.defaults
+from vyos.system.image import is_live_boot
from vyos.utils.file import read_file
from vyos.utils.file import read_json
from vyos.utils.process import popen
-from vyos.utils.process import run
from vyos.utils.process import DEVNULL
version_file = os.path.join(vyos.defaults.directories['data'], 'version.json')
@@ -81,16 +81,14 @@ def get_full_version_data(fname=version_file):
else:
version_data['system_type'] = f"{hypervisor} guest"
- # Get boot type, it can be livecd, installed image, or, possible, a system installed
- # via legacy "install system" mechanism
+ # Get boot type, it can be livecd or installed image
# In installed images, the squashfs image file is named after its image version,
# while on livecd it's just "filesystem.squashfs", that's how we tell a livecd boot
# from an installed image
- boot_via = "installed image"
- if run(""" grep -e '^overlay.*/filesystem.squashfs' /proc/mounts >/dev/null""") == 0:
+ if is_live_boot():
boot_via = "livecd"
- elif run(""" grep '^overlay /' /proc/mounts >/dev/null """) != 0:
- boot_via = "legacy non-image installation"
+ else:
+ boot_via = "installed image"
version_data['boot_via'] = boot_via
# Get hardware details from DMI
diff --git a/python/vyos/xml_ref/__init__.py b/python/vyos/xml_ref/__init__.py
index bf434865d..2ba3da4e8 100644
--- a/python/vyos/xml_ref/__init__.py
+++ b/python/vyos/xml_ref/__init__.py
@@ -53,6 +53,12 @@ def is_valueless(path: list) -> bool:
def is_leaf(path: list) -> bool:
return load_reference().is_leaf(path)
+def owner(path: list) -> str:
+ return load_reference().owner(path)
+
+def priority(path: list) -> str:
+ return load_reference().priority(path)
+
def cli_defined(path: list, node: str, non_local=False) -> bool:
return load_reference().cli_defined(path, node, non_local=non_local)
diff --git a/python/vyos/xml_ref/definition.py b/python/vyos/xml_ref/definition.py
index c90c5ddbc..c85835ffd 100644
--- a/python/vyos/xml_ref/definition.py
+++ b/python/vyos/xml_ref/definition.py
@@ -135,6 +135,33 @@ class Xml:
d = self._get_ref_path(path)
return self._is_leaf_node(d)
+ def _least_upper_data(self, path: list, name: str) -> str:
+ ref_path = path.copy()
+ d = self.ref
+ data = ''
+ while ref_path and d:
+ d = d.get(ref_path[0], {})
+ ref_path.pop(0)
+ if self._is_tag_node(d) and ref_path:
+ ref_path.pop(0)
+ if self._is_leaf_node(d) and ref_path:
+ ref_path.pop(0)
+ res = self._get_ref_node_data(d, name)
+ if res is not None:
+ data = res
+
+ return data
+
+ def owner(self, path: list) -> str:
+ from pathlib import Path
+ data = self._least_upper_data(path, 'owner')
+ if data:
+ data = Path(data.split()[0]).name
+ return data
+
+ def priority(self, path: list) -> str:
+ return self._least_upper_data(path, 'priority')
+
@staticmethod
def _dict_get(d: dict, path: list) -> dict:
for i in path:
diff --git a/scripts/check-pr-title-and-commit-messages.py b/scripts/check-pr-title-and-commit-messages.py
deleted file mode 100755
index 001f6cf82..000000000
--- a/scripts/check-pr-title-and-commit-messages.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python3
-
-import re
-import sys
-import time
-
-import requests
-
-# Use the same regex for PR title and commit messages for now
-title_regex = r'^(([a-zA-Z\-_.]+:\s)?)T\d+:\s+[^\s]+.*'
-commit_regex = title_regex
-
-def check_pr_title(title):
- if not re.match(title_regex, title):
- print("PR title '{}' does not match the required format!".format(title))
- print("Valid title example: T99999: make IPsec secure")
- sys.exit(1)
-
-def check_commit_message(title):
- if not re.match(commit_regex, title):
- print("Commit title '{}' does not match the required format!".format(title))
- print("Valid title example: T99999: make IPsec secure")
- sys.exit(1)
-
-if __name__ == '__main__':
- if len(sys.argv) < 2:
- print("Please specify pull request URL!")
- sys.exit(1)
-
- # There seems to be a race condition that causes this scripts to receive
- # an incomplete PR object that is missing certain fields,
- # which causes temporary CI failures that require re-running the script
- #
- # It's probably better to add a small delay to prevent that
- time.sleep(5)
-
- # Get the pull request object
- pr = requests.get(sys.argv[1]).json()
- if "title" not in pr:
- print("The PR object does not have a title field!")
- print("Did not receive a valid pull request object, please check the URL!")
- sys.exit(1)
-
- check_pr_title(pr["title"])
-
- # Get the list of commits
- commits = requests.get(pr["commits_url"]).json()
- for c in commits:
- # Retrieve every individual commit and check its title
- co = requests.get(c["url"]).json()
- check_commit_message(co["commit"]["message"])
diff --git a/smoketest/config-tests/container-simple b/smoketest/config-tests/container-simple
index 299af64cb..cc80ef4cf 100644
--- a/smoketest/config-tests/container-simple
+++ b/smoketest/config-tests/container-simple
@@ -8,5 +8,6 @@ set container name c01 capability 'net-bind-service'
set container name c01 capability 'net-raw'
set container name c01 image 'busybox:stable'
set container name c02 allow-host-networks
+set container name c02 allow-host-pid
set container name c02 capability 'sys-time'
set container name c02 image 'busybox:stable'
diff --git a/smoketest/config-tests/nat-basic b/smoketest/config-tests/nat-basic
new file mode 100644
index 000000000..9fea08b02
--- /dev/null
+++ b/smoketest/config-tests/nat-basic
@@ -0,0 +1,85 @@
+set interfaces ethernet eth0 offload rps
+set interfaces ethernet eth0 disable
+set interfaces ethernet eth1 offload gro
+set interfaces ethernet eth1 offload gso
+set interfaces ethernet eth1 offload rps
+set interfaces ethernet eth1 offload sg
+set interfaces ethernet eth1 offload tso
+set interfaces ethernet eth2 offload gro
+set interfaces ethernet eth2 offload gso
+set interfaces ethernet eth2 offload rps
+set interfaces ethernet eth2 offload sg
+set interfaces ethernet eth2 offload tso
+set interfaces ethernet eth3 offload gro
+set interfaces ethernet eth3 offload gso
+set interfaces ethernet eth3 offload rps
+set interfaces ethernet eth3 offload sg
+set interfaces ethernet eth3 offload tso
+set interfaces bonding bond10 hash-policy 'layer3+4'
+set interfaces bonding bond10 member interface 'eth2'
+set interfaces bonding bond10 member interface 'eth3'
+set interfaces bonding bond10 mode '802.3ad'
+set interfaces bonding bond10 vif 50 address '192.168.189.1/24'
+set interfaces loopback lo
+set interfaces pppoe pppoe7 authentication password 'vyos'
+set interfaces pppoe pppoe7 authentication username 'vyos'
+set interfaces pppoe pppoe7 dhcpv6-options pd 0 interface bond10.50 address '1'
+set interfaces pppoe pppoe7 dhcpv6-options pd 0 length '56'
+set interfaces pppoe pppoe7 ip adjust-mss '1452'
+set interfaces pppoe pppoe7 ipv6 address autoconf
+set interfaces pppoe pppoe7 ipv6 adjust-mss '1432'
+set interfaces pppoe pppoe7 mtu '1492'
+set interfaces pppoe pppoe7 no-peer-dns
+set interfaces pppoe pppoe7 source-interface 'eth1'
+set service lldp interface eth1 disable
+set service ntp allow-client address '192.168.189.0/24'
+set service ntp server time1.vyos.net
+set service ntp server time2.vyos.net
+set service ntp listen-address '192.168.189.1'
+set service ssh dynamic-protection
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 lease '604800'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option default-router '192.168.189.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '1.1.1.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '9.9.9.9'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 start '192.168.189.20'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 stop '192.168.189.254'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 subnet-id '1'
+set service router-advert interface bond10.50 prefix ::/64 preferred-lifetime '2700'
+set service router-advert interface bond10.50 prefix ::/64 valid-lifetime '5400'
+set system config-management commit-revisions '100'
+set system domain-name 'vyos.net'
+set system host-name 'R1'
+set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
+set system login user vyos authentication plaintext-password ''
+set system name-server '1.1.1.1'
+set system name-server '9.9.9.9'
+set system console device ttyS0 speed '115200'
+set nat destination rule 1000 destination port '3389'
+set nat destination rule 1000 inbound-interface name 'pppoe7'
+set nat destination rule 1000 protocol 'tcp'
+set nat destination rule 1000 translation address '192.168.189.5'
+set nat destination rule 1000 translation port '3389'
+set nat destination rule 10022 destination port '10022'
+set nat destination rule 10022 inbound-interface name 'pppoe7'
+set nat destination rule 10022 protocol 'tcp'
+set nat destination rule 10022 translation address '192.168.189.2'
+set nat destination rule 10022 translation port '22'
+set nat destination rule 10300 destination port '10300'
+set nat destination rule 10300 inbound-interface name 'pppoe7'
+set nat destination rule 10300 protocol 'udp'
+set nat destination rule 10300 translation address '192.168.189.2'
+set nat destination rule 10300 translation port '10300'
+set nat source rule 10 outbound-interface name 'eth1'
+set nat source rule 10 source address '192.168.189.0/24'
+set nat source rule 10 translation address 'masquerade'
+set nat source rule 10 translation options port-mapping 'random'
+set nat source rule 50 outbound-interface name 'pppoe7'
+set nat source rule 50 protocol 'udp'
+set nat source rule 50 source address '192.168.189.2'
+set nat source rule 50 source port '10300'
+set nat source rule 50 translation address 'masquerade'
+set nat source rule 50 translation port '10300'
+set nat source rule 100 outbound-interface name 'pppoe7'
+set nat source rule 100 source address '192.168.189.0/24'
+set nat source rule 100 translation address 'masquerade'
diff --git a/smoketest/configs/container-simple b/smoketest/configs/container-simple
index 05efe05e9..82983afb7 100644
--- a/smoketest/configs/container-simple
+++ b/smoketest/configs/container-simple
@@ -7,6 +7,7 @@ container {
}
name c02 {
allow-host-networks
+ allow-host-pid
cap-add sys-time
image busybox:stable
}
diff --git a/smoketest/configs/nat-basic b/smoketest/configs/nat-basic
new file mode 100644
index 000000000..52f369f34
--- /dev/null
+++ b/smoketest/configs/nat-basic
@@ -0,0 +1,256 @@
+interfaces {
+ bonding bond10 {
+ hash-policy "layer3+4"
+ member {
+ interface "eth2"
+ interface "eth3"
+ }
+ mode "802.3ad"
+ vif 50 {
+ address "192.168.189.1/24"
+ }
+ }
+ ethernet eth0 {
+ disable
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth1 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth2 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth3 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ loopback lo {
+ }
+ pppoe pppoe7 {
+ authentication {
+ password "vyos"
+ username "vyos"
+ }
+ dhcpv6-options {
+ pd 0 {
+ interface bond10.50 {
+ address "1"
+ }
+ length "56"
+ }
+ }
+ ip {
+ adjust-mss "1452"
+ }
+ ipv6 {
+ address {
+ autoconf
+ }
+ adjust-mss "1432"
+ }
+ mtu "1492"
+ no-peer-dns
+ source-interface "eth1"
+ }
+}
+nat {
+ destination {
+ rule 1000 {
+ destination {
+ port "3389"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "tcp"
+ translation {
+ address "192.168.189.5"
+ port "3389"
+ }
+ }
+ rule 10022 {
+ destination {
+ port "10022"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "tcp"
+ translation {
+ address "192.168.189.2"
+ port "22"
+ }
+ }
+ rule 10300 {
+ destination {
+ port "10300"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "udp"
+ translation {
+ address "192.168.189.2"
+ port "10300"
+ }
+ }
+ }
+ source {
+ rule 10 {
+ outbound-interface {
+ name "eth1"
+ }
+ source {
+ address "192.168.189.0/24"
+ }
+ translation {
+ address "masquerade"
+ options {
+ port-mapping fully-random
+ }
+ }
+ }
+ rule 50 {
+ outbound-interface {
+ name "pppoe7"
+ }
+ protocol "udp"
+ source {
+ address "192.168.189.2"
+ port "10300"
+ }
+ translation {
+ address "masquerade"
+ port "10300"
+ }
+ }
+ rule 100 {
+ outbound-interface {
+ name "pppoe7"
+ }
+ source {
+ address "192.168.189.0/24"
+ }
+ translation {
+ address "masquerade"
+ }
+ }
+ }
+}
+service {
+ dhcp-server {
+ shared-network-name LAN {
+ subnet 192.168.189.0/24 {
+ default-router "192.168.189.1"
+ domain-name "vyos.net"
+ lease "604800"
+ name-server "1.1.1.1"
+ name-server "9.9.9.9"
+ range 0 {
+ start "192.168.189.20"
+ stop "192.168.189.254"
+ }
+ }
+ }
+ }
+ lldp {
+ interface all {
+ }
+ interface eth1 {
+ disable
+ }
+ }
+ ntp {
+ allow-client {
+ address "192.168.189.0/24"
+ }
+ listen-address "192.168.189.1"
+ server time1.vyos.net {
+ }
+ server time2.vyos.net {
+ }
+ }
+ router-advert {
+ interface bond10.50 {
+ prefix ::/64 {
+ preferred-lifetime "2700"
+ valid-lifetime "5400"
+ }
+ }
+ }
+ ssh {
+ disable-host-validation
+ dynamic-protection {
+ }
+ }
+}
+system {
+ config-management {
+ commit-revisions "100"
+ }
+ conntrack {
+ modules {
+ ftp
+ h323
+ nfs
+ pptp
+ sip
+ sqlnet
+ tftp
+ }
+ }
+ console {
+ device ttyS0 {
+ speed "115200"
+ }
+ }
+ domain-name "vyos.net"
+ host-name "R1"
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
+ plaintext-password ""
+ }
+ }
+ }
+ name-server "1.1.1.1"
+ name-server "9.9.9.9"
+ syslog {
+ global {
+ facility all {
+ level "info"
+ }
+ facility local7 {
+ level "debug"
+ }
+ }
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@7:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
+// Release version: 1.4.0-epa3
diff --git a/smoketest/scripts/cli/base_accel_ppp_test.py b/smoketest/scripts/cli/base_accel_ppp_test.py
index 383adc445..ab723e707 100644
--- a/smoketest/scripts/cli/base_accel_ppp_test.py
+++ b/smoketest/scripts/cli/base_accel_ppp_test.py
@@ -367,6 +367,27 @@ class BasicAccelPPPTest:
]
)
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "backup",
+ ]
+ )
+
+ self.set(
+ [
+ "authentication",
+ "radius",
+ "server",
+ radius_server,
+ "priority",
+ "10",
+ ]
+ )
+
# commit changes
self.cli_commit()
@@ -379,6 +400,8 @@ class BasicAccelPPPTest:
self.assertEqual(f"acct-port=0", server[3])
self.assertEqual(f"req-limit=0", server[4])
self.assertEqual(f"fail-time=0", server[5])
+ self.assertIn('weight=10', server)
+ self.assertIn('backup', server)
def test_accel_ipv4_pool(self):
self.basic_config(is_gateway=False, is_client_pool=False)
diff --git a/smoketest/scripts/cli/test_cgnat.py b/smoketest/scripts/cli/test_cgnat.py
new file mode 100755
index 000000000..c65c58820
--- /dev/null
+++ b/smoketest/scripts/cli/test_cgnat.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+from vyos.configsession import ConfigSessionError
+
+
+base_path = ['nat', 'cgnat']
+nftables_cgnat_config = '/run/nftables-cgnat.nft'
+
+
+class TestCGNAT(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(TestCGNAT, 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):
+ self.cli_delete(base_path)
+ self.cli_commit()
+ self.assertFalse(os.path.exists(nftables_cgnat_config))
+
+ def test_cgnat(self):
+ internal_name = 'vyos-int-01'
+ external_name = 'vyos-ext-01'
+ internal_net = '100.64.0.0/29'
+ external_net = '192.0.2.1-192.0.2.2'
+ external_ports = '40000-60000'
+ ports_per_subscriber = '5000'
+ rule = '100'
+
+ nftables_search = [
+ ['map tcp_nat_map'],
+ ['map udp_nat_map'],
+ ['map icmp_nat_map'],
+ ['map other_nat_map'],
+ ['100.64.0.0 : 192.0.2.1 . 40000-44999'],
+ ['100.64.0.1 : 192.0.2.1 . 45000-49999'],
+ ['100.64.0.2 : 192.0.2.1 . 50000-54999'],
+ ['100.64.0.3 : 192.0.2.1 . 55000-59999'],
+ ['100.64.0.4 : 192.0.2.2 . 40000-44999'],
+ ['100.64.0.5 : 192.0.2.2 . 45000-49999'],
+ ['100.64.0.6 : 192.0.2.2 . 50000-54999'],
+ ['100.64.0.7 : 192.0.2.2 . 55000-59999'],
+ ['chain POSTROUTING'],
+ ['type nat hook postrouting priority srcnat'],
+ ['ip protocol tcp counter snat ip to ip saddr map @tcp_nat_map'],
+ ['ip protocol udp counter snat ip to ip saddr map @udp_nat_map'],
+ ['ip protocol icmp counter snat ip to ip saddr map @icmp_nat_map'],
+ ['counter snat ip to ip saddr map @other_nat_map'],
+ ]
+
+ self.cli_set(base_path + ['pool', 'external', external_name, 'external-port-range', external_ports])
+ self.cli_set(base_path + ['pool', 'external', external_name, 'range', external_net])
+
+ # allocation out of the available ports
+ with self.assertRaises(ConfigSessionError):
+ self.cli_set(base_path + ['pool', 'external', external_name, 'per-user-limit', 'port', '8000'])
+ self.cli_commit()
+ self.cli_set(base_path + ['pool', 'external', external_name, 'per-user-limit', 'port', ports_per_subscriber])
+
+ # internal pool not set
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_set(base_path + ['pool', 'internal', internal_name, 'range', internal_net])
+
+ self.cli_set(base_path + ['rule', rule, 'source', 'pool', internal_name])
+ # non-exist translation pool
+ with self.assertRaises(ConfigSessionError):
+ self.cli_set(base_path + ['rule', rule, 'translation', 'pool', 'fake-pool'])
+ self.cli_commit()
+
+ self.cli_set(base_path + ['rule', rule, 'translation', 'pool', external_name])
+ self.cli_commit()
+
+ self.verify_nftables(nftables_search, 'ip cgnat', inverse=False, args='-s')
+
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py
index 419de774a..f436424b8 100755
--- a/smoketest/scripts/cli/test_interfaces_bonding.py
+++ b/smoketest/scripts/cli/test_interfaces_bonding.py
@@ -241,6 +241,34 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase):
for member in self._members:
self.assertIn(member, slaves)
+ def test_bonding_system_mac(self):
+ # configure member interfaces and system-mac
+ default_system_mac = '00:00:00:00:00:00' # default MAC is all zeroes
+ system_mac = '00:50:ab:cd:ef:11'
+
+ for interface in self._interfaces:
+ for option in self._options.get(interface, []):
+ self.cli_set(self._base_path + [interface] + option.split())
+
+ self.cli_set(self._base_path + [interface, 'system-mac', system_mac])
+
+ self.cli_commit()
+
+ # verify config
+ for interface in self._interfaces:
+ tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system')
+ self.assertIn(tmp, system_mac)
+
+ for interface in self._interfaces:
+ self.cli_delete(self._base_path + [interface, 'system-mac'])
+
+ self.cli_commit()
+
+ # verify default value
+ for interface in self._interfaces:
+ tmp = read_file(f'/sys/class/net/{interface}/bonding/ad_actor_system')
+ self.assertIn(tmp, default_system_mac)
+
def test_bonding_evpn_multihoming(self):
id = '5'
for interface in self._interfaces:
diff --git a/smoketest/scripts/cli/test_interfaces_ethernet.py b/smoketest/scripts/cli/test_interfaces_ethernet.py
index 8f387b23d..4843a40da 100755
--- a/smoketest/scripts/cli/test_interfaces_ethernet.py
+++ b/smoketest/scripts/cli/test_interfaces_ethernet.py
@@ -354,5 +354,15 @@ class EthernetInterfaceTest(BasicInterfaceTest.TestCase):
out = loads(out)
self.assertFalse(out[0]['autonegotiate'])
+ def test_ethtool_evpn_uplink_tarcking(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='zebra')
+ self.assertIn(f' evpn mh uplink', frrconfig)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_interfaces_wireless.py b/smoketest/scripts/cli/test_interfaces_wireless.py
index 83b00ac0c..b45754cae 100755
--- a/smoketest/scripts/cli/test_interfaces_wireless.py
+++ b/smoketest/scripts/cli/test_interfaces_wireless.py
@@ -236,6 +236,17 @@ class WirelessInterfaceTest(BasicInterfaceTest.TestCase):
self.assertIn(interface, bridge_members)
+ # Now generate a VLAN on the bridge
+ self.cli_set(bridge_path + ['enable-vlan'])
+ self.cli_set(bridge_path + ['vif', '20', 'address', '10.0.0.1/24'])
+
+ self.cli_commit()
+
+ tmp = get_config_value(interface, 'bridge')
+ self.assertEqual(tmp, bridge)
+ tmp = get_config_value(interface, 'wds_sta')
+ self.assertEqual(tmp, '1')
+
self.cli_delete(bridge_path)
def test_wireless_security_station_address(self):
diff --git a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
index c8b17316f..370a9276a 100755
--- a/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
+++ b/smoketest/scripts/cli/test_load-balancing_reverse-proxy.py
@@ -385,5 +385,26 @@ class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'mode {mode}', config)
self.assertIn(f'server {bk_name} {bk_server}:{bk_server_port}', config)
+ def test_07_lb_reverse_proxy_http_response_headers(self):
+ # Setup base
+ self.configure_pki()
+ self.base_config()
+
+ # Set example headers in both frontend and backend
+ self.cli_set(base_path + ['service', 'https_front', 'http-response-headers', 'Cache-Control', 'value', 'max-age=604800'])
+ self.cli_set(base_path + ['backend', 'bk-01', 'http-response-headers', 'Proxy-Backend-ID', 'value', 'bk-01'])
+ self.cli_commit()
+
+ # Test headers are present in generated configuration file
+ config = read_file(HAPROXY_CONF)
+ self.assertIn('http-response set-header Cache-Control \'max-age=604800\'', config)
+ self.assertIn('http-response set-header Proxy-Backend-ID \'bk-01\'', config)
+
+ # Test setting alongside modes other than http is blocked by validation conditions
+ self.cli_set(base_path + ['service', 'https_front', 'mode', 'tcp'])
+ with self.assertRaises(ConfigSessionError) as e:
+ self.cli_commit()
+
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index ee4445251..a0c6ab055 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -1923,6 +1923,66 @@ class TestPolicy(VyOSUnitTestSHIM.TestCase):
self.assertEqual(sort_ip(tmp), sort_ip(original_second))
+ def test_frr_individual_remove_T6283_T6250(self):
+ path = base_path + ['route-map']
+ route_maps = ['RMAP-1', 'RMAP_2']
+ seq = '10'
+ base_local_preference = 300
+ base_table = 50
+
+ # T6250
+ local_preference = base_local_preference
+ table = base_table
+ for route_map in route_maps:
+ self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit'])
+ self.cli_set(path + [route_map, 'rule', seq, 'set', 'table', str(table)])
+ self.cli_set(path + [route_map, 'rule', seq, 'set', 'local-preference', str(local_preference)])
+ local_preference += 20
+ table += 5
+
+ self.cli_commit()
+
+ local_preference = base_local_preference
+ table = base_table
+ for route_map in route_maps:
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ self.assertIn(f' set local-preference {local_preference}', config)
+ self.assertIn(f' set table {table}', config)
+ local_preference += 20
+ table += 5
+
+ for route_map in route_maps:
+ self.cli_delete(path + [route_map, 'rule', '10', 'set', 'table'])
+ # we explicitly commit multiple times to be as vandal as possible to the system
+ self.cli_commit()
+
+ local_preference = base_local_preference
+ for route_map in route_maps:
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ self.assertIn(f' set local-preference {local_preference}', config)
+ local_preference += 20
+
+ # T6283
+ seq = '20'
+ prepend = '100 100 100'
+ for route_map in route_maps:
+ self.cli_set(path + [route_map, 'rule', seq, 'action', 'permit'])
+ self.cli_set(path + [route_map, 'rule', seq, 'set', 'as-path', 'prepend', prepend])
+
+ self.cli_commit()
+
+ for route_map in route_maps:
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ self.assertIn(f' set as-path prepend {prepend}', config)
+
+ for route_map in route_maps:
+ self.cli_delete(path + [route_map, 'rule', seq, 'set'])
+ # we explicitly commit multiple times to be as vandal as possible to the system
+ self.cli_commit()
+
+ for route_map in route_maps:
+ config = self.getFRRconfig(f'route-map {route_map} permit {seq}', end='')
+ self.assertNotIn(f' set', config)
def sort_ip(output):
o = '\n'.join([' '.join(line.strip().split()) for line in output.strip().splitlines()])
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index 03daa34aa..ea2f561a4 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -1330,6 +1330,27 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f'neighbor {ext_neighbors[1]} remote-as external', conf)
self.assertIn(f'neighbor {ext_neighbors[1]} peer-group {ext_pg_name}', conf)
+ def test_bgp_29_peer_group_remote_as_equal_local_as(self):
+ self.cli_set(base_path + ['system-as', ASN])
+ self.cli_set(base_path + ['peer-group', 'OVERLAY', 'local-as', f'{int(ASN) + 1}'])
+ self.cli_set(base_path + ['peer-group', 'OVERLAY', 'remote-as', f'{int(ASN) + 1}'])
+ self.cli_set(base_path + ['peer-group', 'OVERLAY', 'address-family', 'l2vpn-evpn'])
+
+ self.cli_set(base_path + ['peer-group', 'UNDERLAY', 'address-family', 'ipv4-unicast'])
+
+ self.cli_set(base_path + ['neighbor', '10.177.70.62', 'peer-group', 'UNDERLAY'])
+ self.cli_set(base_path + ['neighbor', '10.177.70.62', 'remote-as', 'external'])
+
+ self.cli_set(base_path + ['neighbor', '10.177.75.1', 'peer-group', 'OVERLAY'])
+ self.cli_set(base_path + ['neighbor', '10.177.75.2', 'peer-group', 'OVERLAY'])
+
+ self.cli_commit()
+
+ conf = self.getFRRconfig(f'router bgp {ASN}')
+
+ self.assertIn(f'neighbor OVERLAY remote-as {int(ASN) + 1}', conf)
+ self.assertIn(f'neighbor OVERLAY local-as {int(ASN) + 1}', conf)
+
def test_bgp_99_bmp(self):
target_name = 'instance-bmp'
target_address = '127.0.0.1'
diff --git a/smoketest/scripts/cli/test_protocols_ospf.py b/smoketest/scripts/cli/test_protocols_ospf.py
index 1b9cc50fe..585c1dc89 100755
--- a/smoketest/scripts/cli/test_protocols_ospf.py
+++ b/smoketest/scripts/cli/test_protocols_ospf.py
@@ -16,6 +16,7 @@
import unittest
+from time import sleep
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
@@ -480,6 +481,8 @@ class TestProtocolsOSPF(VyOSUnitTestSHIM.TestCase):
# Commit main OSPF changes
self.cli_commit()
+ sleep(10)
+
# Verify main OSPF changes
frrconfig = self.getFRRconfig('router ospf', daemon=PROCESS_NAME)
self.assertIn(f'router ospf', frrconfig)
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index fef1ff23a..5977b2f41 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -441,7 +441,6 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
def test_08_random_detect(self):
- self.skipTest('tc returns invalid JSON here - needs iproute2 fix')
bandwidth = 5000
first = True
@@ -467,8 +466,11 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
bandwidth = 5000
for interface in self._interfaces:
tmp = get_tc_qdisc_json(interface)
- import pprint
- pprint.pprint(tmp)
+ self.assertTrue('gred' in tmp.get('kind'))
+ self.assertEqual(8, len(tmp.get('options', {}).get('vqs')))
+ self.assertEqual(8, tmp.get('options', {}).get('dp_cnt'))
+ self.assertEqual(0, tmp.get('options', {}).get('dp_default'))
+ self.assertTrue(tmp.get('options', {}).get('grio'))
def test_09_rate_control(self):
bandwidth = 5000
@@ -736,6 +738,27 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.assertEqual('', cmd(f'tc filter show dev {interface}'))
+ def test_14_policy_limiter_marked_traffic(self):
+ policy_name = 'smoke_test'
+ base_policy_path = ['qos', 'policy', 'limiter', policy_name]
+
+ self.cli_set(['qos', 'interface', self._interfaces[0], 'ingress', policy_name])
+ self.cli_set(base_policy_path + ['class', '100', 'bandwidth', '20gbit'])
+ self.cli_set(base_policy_path + ['class', '100', 'burst', '3760k'])
+ self.cli_set(base_policy_path + ['class', '100', 'match', 'INTERNAL', 'mark', '100'])
+ self.cli_set(base_policy_path + ['class', '100', 'priority', '20'])
+ self.cli_set(base_policy_path + ['default', 'bandwidth', '1gbit'])
+ self.cli_set(base_policy_path + ['default', 'burst', '125000000b'])
+ self.cli_commit()
+
+ tc_filters = cmd(f'tc filter show dev {self._interfaces[0]} ingress')
+ # class 100
+ self.assertIn('filter parent ffff: protocol all pref 20 fw chain 0', tc_filters)
+ self.assertIn('action order 1: police 0x1 rate 20Gbit burst 3847500b mtu 2Kb action drop overhead 0b', tc_filters)
+ # default
+ self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters)
+ self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_service_pppoe-server.py b/smoketest/scripts/cli/test_service_pppoe-server.py
index 5a48b1f58..97c63d4cb 100755
--- a/smoketest/scripts/cli/test_service_pppoe-server.py
+++ b/smoketest/scripts/cli/test_service_pppoe-server.py
@@ -168,7 +168,14 @@ class TestServicePPPoEServer(BasicAccelPPPTest.TestCase):
conf = ConfigParser(allow_no_value=True, delimiters='=')
conf.read(self._config_file)
- self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,-1:300')
+ self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300')
+
+ self.set(['pado-delay', 'disable', 'sessions', '400'])
+ self.cli_commit()
+
+ conf = ConfigParser(allow_no_value=True, delimiters='=')
+ conf.read(self._config_file)
+ self.assertEqual(conf['pppoe']['pado-delay'], '10,20:200,30:300,-1:400')
if __name__ == '__main__':
diff --git a/smoketest/scripts/cli/test_service_upnp.py b/smoketest/scripts/cli/test_service_upnp.py
deleted file mode 100755
index fd67b0ced..000000000
--- a/smoketest/scripts/cli/test_service_upnp.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-#
-# 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
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import unittest
-
-from base_vyostest_shim import VyOSUnitTestSHIM
-
-from vyos.configsession import ConfigSessionError
-from vyos.template import ip_from_cidr
-from vyos.utils.file import read_file
-from vyos.utils.process import process_named_running
-
-UPNP_CONF = '/run/upnp/miniupnp.conf'
-DAEMON = 'miniupnpd'
-interface = 'eth0'
-base_path = ['service', 'upnp']
-address_base = ['interfaces', 'ethernet', interface, 'address']
-
-ipv4_addr = '100.64.0.1/24'
-ipv6_addr = '2001:db8::1/64'
-
-class TestServiceUPnP(VyOSUnitTestSHIM.TestCase):
- @classmethod
- def setUpClass(cls):
- super(TestServiceUPnP, 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_set(cls, address_base + [ipv4_addr])
- cls.cli_set(cls, address_base + [ipv6_addr])
-
- @classmethod
- def tearDownClass(cls):
- cls.cli_delete(cls, address_base)
- cls._session.commit()
-
- super(TestServiceUPnP, cls).tearDownClass()
-
- def tearDown(self):
- # Check for running process
- self.assertTrue(process_named_running(DAEMON))
-
- self.cli_delete(base_path)
- self.cli_commit()
-
- # Check for running process
- self.assertFalse(process_named_running(DAEMON))
-
- def test_ipv4_base(self):
- self.cli_set(base_path + ['nat-pmp'])
- self.cli_set(base_path + ['listen', interface])
-
- # check validate() - WAN interface is mandatory
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_set(base_path + ['wan-interface', interface])
-
- self.cli_commit()
-
- config = read_file(UPNP_CONF)
- self.assertIn(f'ext_ifname={interface}', config)
- self.assertIn(f'listening_ip={interface}', config)
- self.assertIn(f'enable_natpmp=yes', config)
- self.assertIn(f'enable_upnp=yes', config)
-
- def test_ipv6_base(self):
- v6_addr = ip_from_cidr(ipv6_addr)
-
- self.cli_set(base_path + ['nat-pmp'])
- self.cli_set(base_path + ['listen', interface])
- self.cli_set(base_path + ['listen', v6_addr])
-
- # check validate() - WAN interface is mandatory
- with self.assertRaises(ConfigSessionError):
- self.cli_commit()
- self.cli_set(base_path + ['wan-interface', interface])
-
- self.cli_commit()
-
- config = read_file(UPNP_CONF)
- self.assertIn(f'ext_ifname={interface}', config)
- self.assertIn(f'listening_ip={interface}', config)
- self.assertIn(f'ipv6_listening_ip={v6_addr}', config)
- self.assertIn(f'enable_natpmp=yes', config)
- self.assertIn(f'enable_upnp=yes', config)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_l2tp.py b/smoketest/scripts/cli/test_vpn_l2tp.py
index 8c4e53895..07a7e2906 100755
--- a/smoketest/scripts/cli/test_vpn_l2tp.py
+++ b/smoketest/scripts/cli/test_vpn_l2tp.py
@@ -95,6 +95,29 @@ class TestVPNL2TPServer(BasicAccelPPPTest.TestCase):
self.cli_set(base_path + ['authentication', 'protocols', 'chap'])
self.cli_commit()
+ def test_l2tp_radius_server(self):
+ base_path = ['vpn', 'l2tp', 'remote-access']
+ radius_server = "192.0.2.22"
+ radius_key = "secretVyOS"
+
+ self.cli_set(base_path + ['authentication', 'mode', 'radius'])
+ self.cli_set(base_path + ['gateway-address', '192.0.2.1'])
+ self.cli_set(base_path + ['client-ip-pool', 'SIMPLE-POOL', 'range', '192.0.2.0/24'])
+ self.cli_set(base_path + ['default-pool', 'SIMPLE-POOL'])
+ self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'key', radius_key])
+ self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'priority', '10'])
+ self.cli_set(base_path + ['authentication', 'radius', 'server', radius_server, 'backup'])
+
+ # commit changes
+ self.cli_commit()
+
+ # Validate configuration values
+ conf = ConfigParser(allow_no_value=True)
+ conf.read(self._config_file)
+ server = conf["radius"]["server"].split(",")
+ self.assertIn('weight=10', server)
+ self.assertIn('backup', server)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vpn_sstp.py b/smoketest/scripts/cli/test_vpn_sstp.py
index f0695d577..1a3e1df6e 100755
--- a/smoketest/scripts/cli/test_vpn_sstp.py
+++ b/smoketest/scripts/cli/test_vpn_sstp.py
@@ -75,6 +75,16 @@ class TestVPNSSTPServer(BasicAccelPPPTest.TestCase):
config = read_file(self._config_file)
self.assertIn(f'port={port}', config)
+ def test_sstp_host_name(self):
+ host_name = 'test.vyos.io'
+ self.set(['host-name', host_name])
+
+ self.basic_config()
+ self.cli_commit()
+
+ config = read_file(self._config_file)
+ self.assertIn(f'host-name={host_name}', config)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index f6e4181c0..243397dc2 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -18,7 +18,6 @@ import re
import os
import unittest
-from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
from vyos.configsession import ConfigSessionError
@@ -27,6 +26,7 @@ from vyos.ifconfig import Section
from vyos.utils.file import read_file
from vyos.utils.network import get_interface_config
from vyos.utils.network import is_intf_addr_assigned
+from vyos.utils.network import interface_exists
from vyos.utils.system import sysctl_read
base_path = ['vrf']
@@ -60,7 +60,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_delete(base_path)
self.cli_commit()
for vrf in vrfs:
- self.assertNotIn(vrf, interfaces())
+ self.assertFalse(interface_exists(vrf))
def test_vrf_vni_and_table_id(self):
base_table = '1000'
@@ -89,7 +89,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
iproute2_config = read_file('/etc/iproute2/rt_tables.d/vyos-vrf.conf')
for vrf in vrfs:
description = f'VyOS-VRF-{vrf}'
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
vrf_if = Interface(vrf)
# validate proper interface description
self.assertEqual(vrf_if.get_alias(), description)
@@ -131,7 +131,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
loopbacks = ['127.0.0.1', '::1']
for vrf in vrfs:
# Ensure VRF was created
- self.assertIn(vrf, interfaces())
+ self.assertTrue(interface_exists(vrf))
# Verify IP forwarding is 1 (enabled)
self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '1')
self.assertEqual(sysctl_read(f'net.ipv6.conf.{vrf}.forwarding'), '1')
@@ -171,7 +171,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
# Check if VRF has been created
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
table = str(int(table) + 1)
self.cli_set(base + ['table', table])
@@ -228,7 +228,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
next_hop = f'192.0.{table}.1'
prefix = f'10.0.{table}.0/24'
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
@@ -261,7 +261,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Apply VRF config
self.cli_commit()
# Ensure VRF got created
- self.assertIn(vrf, interfaces())
+ self.assertTrue(interface_exists(vrf))
# ... and IP addresses are still assigned
for address in addresses:
self.assertTrue(is_intf_addr_assigned(interface, address))
@@ -293,7 +293,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
loopbacks = ['127.0.0.1', '::1']
for vrf in vrfs:
# Ensure VRF was created
- self.assertIn(vrf, interfaces())
+ self.assertTrue(interface_exists(vrf))
# Verify IP forwarding is 0 (disabled)
self.assertEqual(sysctl_read(f'net.ipv4.conf.{vrf}.forwarding'), '0')
self.assertEqual(sysctl_read(f'net.ipv6.conf.{vrf}.forwarding'), '0')
@@ -425,7 +425,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify VRF configuration
table = base_table
for vrf in vrfs:
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
@@ -447,7 +447,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify VRF configuration
table = base_table
for vrf in vrfs:
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
@@ -470,13 +470,39 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify VRF configuration
table = base_table
for vrf in vrfs:
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertIn(f' vni {table}', frrconfig)
# Increment table ID for the next run
table = str(int(table) + 2)
+
+ # add a new VRF with VNI - this must not delete any existing VRF/VNI
+ purple = 'purple'
+ table = str(int(table) + 10)
+ self.cli_set(base_path + ['name', purple, 'table', table])
+ self.cli_set(base_path + ['name', purple, 'vni', table])
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = base_table
+ for vrf in vrfs:
+ self.assertTrue(interface_exists(vrf))
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+ # Increment table ID for the next run
+ table = str(int(table) + 2)
+
+ # Verify purple VRF/VNI
+ self.assertTrue(interface_exists(purple))
+ table = str(int(table) + 10)
+ frrconfig = self.getFRRconfig(f'vrf {purple}')
+ self.assertIn(f' vni {table}', frrconfig)
+
# Now delete all the VNIs
for vrf in vrfs:
base = base_path + ['name', vrf]
@@ -487,11 +513,16 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# Verify no VNI is defined
for vrf in vrfs:
- self.assertTrue(vrf in interfaces())
+ self.assertTrue(interface_exists(vrf))
frrconfig = self.getFRRconfig(f'vrf {vrf}')
self.assertNotIn('vni', frrconfig)
+ # Verify purple VNI remains
+ self.assertTrue(interface_exists(purple))
+ frrconfig = self.getFRRconfig(f'vrf {purple}')
+ self.assertIn(f' vni {table}', frrconfig)
+
def test_vrf_ip_ipv6_nht(self):
table = '6910'
diff --git a/smoketest/scripts/system/test_kernel_options.py b/smoketest/scripts/system/test_kernel_options.py
index 0e3cbd0ed..18922d93d 100755
--- a/smoketest/scripts/system/test_kernel_options.py
+++ b/smoketest/scripts/system/test_kernel_options.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020-2023 VyOS maintainers and contributors
+# Copyright (C) 2020-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
@@ -14,28 +14,38 @@
# 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 gzip
import re
import os
import platform
import unittest
-from vyos.utils.process import call
-from vyos.utils.file import read_file
-
kernel = platform.release()
-config = read_file(f'/boot/config-{kernel}')
-CONFIG = '/proc/config.gz'
class TestKernelModules(unittest.TestCase):
""" VyOS makes use of a lot of Kernel drivers, modules and features. The
required modules which are essential for VyOS should be tested that they are
available in the Kernel that is run. """
+ _config_data = None
+
+ @classmethod
+ def setUpClass(cls):
+ import gzip
+ from vyos.utils.process import call
+
+ super(TestKernelModules, cls).setUpClass()
+ CONFIG = '/proc/config.gz'
+
+ if not os.path.isfile(CONFIG):
+ call('sudo modprobe configs')
+
+ with gzip.open(CONFIG, 'rt') as f:
+ cls._config_data = f.read()
+
def test_bond_interface(self):
# The bond/lacp interface must be enabled in the OS Kernel
for option in ['CONFIG_BONDING']:
- tmp = re.findall(f'{option}=(y|m)', config)
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
def test_bridge_interface(self):
@@ -43,7 +53,7 @@ class TestKernelModules(unittest.TestCase):
for option in ['CONFIG_BRIDGE',
'CONFIG_BRIDGE_IGMP_SNOOPING',
'CONFIG_BRIDGE_VLAN_FILTERING']:
- tmp = re.findall(f'{option}=(y|m)', config)
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
def test_dropmon_enabled(self):
@@ -53,47 +63,53 @@ class TestKernelModules(unittest.TestCase):
'CONFIG_BPF_EVENTS=y',
'CONFIG_TRACEPOINTS=y'
]
- if not os.path.isfile(CONFIG):
- call('sudo modprobe configs')
- with gzip.open(CONFIG, 'rt') as f:
- config_data = f.read()
for option in options_to_check:
- self.assertIn(option, config_data,
- f"Option {option} is not present in /proc/config.gz")
+ self.assertIn(option, self._config_data)
def test_synproxy_enabled(self):
options_to_check = [
'CONFIG_NFT_SYNPROXY',
'CONFIG_IP_NF_TARGET_SYNPROXY'
]
- if not os.path.isfile(CONFIG):
- call('sudo modprobe configs')
- with gzip.open(CONFIG, 'rt') as f:
- config_data = f.read()
for option in options_to_check:
- tmp = re.findall(f'{option}=(y|m)', config_data)
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
def test_qemu_support(self):
- for option in ['CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO',
- 'CONFIG_VIRTIO_NET', 'CONFIG_VIRTIO_CONSOLE',
- 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI',
- 'CONFIG_VIRTIO_BALLOON', 'CONFIG_CRYPTO_DEV_VIRTIO',
- 'CONFIG_X86_PLATFORM_DEVICES']:
- tmp = re.findall(f'{option}=(y|m)', config)
+ options_to_check = [
+ 'CONFIG_VIRTIO_BLK', 'CONFIG_SCSI_VIRTIO',
+ 'CONFIG_VIRTIO_NET', 'CONFIG_VIRTIO_CONSOLE',
+ 'CONFIG_VIRTIO', 'CONFIG_VIRTIO_PCI',
+ 'CONFIG_VIRTIO_BALLOON', 'CONFIG_CRYPTO_DEV_VIRTIO',
+ 'CONFIG_X86_PLATFORM_DEVICES'
+ ]
+ for option in options_to_check:
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
def test_vmware_support(self):
for option in ['CONFIG_VMXNET3']:
- tmp = re.findall(f'{option}=(y|m)', config)
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
def test_container_cgroup_support(self):
- for option in ['CONFIG_CGROUPS', 'CONFIG_MEMCG', 'CONFIG_CGROUP_PIDS', 'CONFIG_CGROUP_BPF']:
- tmp = re.findall(f'{option}=(y|m)', config)
+ options_to_check = [
+ 'CONFIG_CGROUPS', 'CONFIG_MEMCG',
+ 'CONFIG_CGROUP_PIDS', 'CONFIG_CGROUP_BPF'
+ ]
+ for option in options_to_check:
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
+ self.assertTrue(tmp)
+
+ def test_ip_routing_support(self):
+ options_to_check = [
+ 'CONFIG_IP_ADVANCED_ROUTER', 'CONFIG_IP_MULTIPLE_TABLES',
+ 'CONFIG_IP_ROUTE_MULTIPATH'
+ ]
+ for option in options_to_check:
+ tmp = re.findall(f'{option}=(y|m)', self._config_data)
self.assertTrue(tmp)
if __name__ == '__main__':
unittest.main(verbosity=2)
-
diff --git a/src/completion/list_esi.sh b/src/completion/list_esi.sh
new file mode 100755
index 000000000..b8373fa57
--- /dev/null
+++ b/src/completion/list_esi.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This script is completion helper to list all valid ESEs that are visible to FRR
+
+esiJson=$(vtysh -c 'show evpn es json')
+echo "$(echo "$esiJson" | jq -r '.[] | .esi')"
diff --git a/src/completion/list_vni.sh b/src/completion/list_vni.sh
new file mode 100755
index 000000000..f8bd4a993
--- /dev/null
+++ b/src/completion/list_vni.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This script is completion helper to list all configured VNIs that are visible to FRR
+
+vniJson=$(vtysh -c 'show evpn vni json')
+echo "$(echo "$vniJson" | jq -r 'keys | .[]')"
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index a73a18ffa..91a10e891 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -329,9 +329,13 @@ def generate_run_arguments(name, container_config):
prop = vol_config['propagation']
volume += f' --volume {svol}:{dvol}:{mode},{prop}'
+ host_pid = ''
+ if 'allow_host_pid' in container_config:
+ host_pid = '--pid host'
+
container_base_cmd = f'--detach --interactive --tty --replace {capabilities} ' \
f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \
- f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid}'
+ f'--name {name} {hostname} {device} {port} {volume} {env_opt} {label} {uid} {host_pid}'
entrypoint = ''
if 'entrypoint' in container_config:
@@ -339,11 +343,6 @@ def generate_run_arguments(name, container_config):
entrypoint = json_write(container_config['entrypoint'].split()).replace('"', "&quot;")
entrypoint = f'--entrypoint &apos;{entrypoint}&apos;'
- hostname = ''
- if 'host_name' in container_config:
- hostname = container_config['host_name']
- hostname = f'--hostname {hostname}'
-
command = ''
if 'command' in container_config:
command = container_config['command'].strip()
diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py
index 371b219c0..5e5d5fba1 100755
--- a/src/conf_mode/interfaces_bonding.py
+++ b/src/conf_mode/interfaces_bonding.py
@@ -33,6 +33,7 @@ from vyos.ifconfig import BondIf
from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig import Section
from vyos.template import render_to_string
+from vyos.utils.assertion import assert_mac
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
from vyos.utils.network import interface_exists
@@ -244,6 +245,16 @@ def verify(bond):
raise ConfigError('primary interface only works for mode active-backup, ' \
'transmit-load-balance or adaptive-load-balance')
+ if 'system_mac' in bond:
+ if bond['mode'] != '802.3ad':
+ raise ConfigError('Actor MAC address only available in 802.3ad mode!')
+
+ system_mac = bond['system_mac']
+ try:
+ assert_mac(system_mac, test_all_zero=False)
+ except:
+ raise ConfigError(f'Cannot use a multicast MAC address "{system_mac}" as system-mac!')
+
return None
def generate(bond):
diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py
index 9789f7bd3..7b2c1ee0b 100755
--- a/src/conf_mode/interfaces_bridge.py
+++ b/src/conf_mode/interfaces_bridge.py
@@ -56,6 +56,17 @@ def get_config(config=None):
bridge['member'].update({'interface_remove' : tmp })
else:
bridge.update({'member' : {'interface_remove' : tmp }})
+ for interface in tmp:
+ # When using VXLAN member interfaces that are configured for Single
+ # VXLAN Device (SVD) we need to call the VXLAN conf-mode script to
+ # re-create VLAN to VNI mappings if required, but only if the interface
+ # is already live on the system - this must not be done on first commit
+ if interface.startswith('vxlan') and interface_exists(interface):
+ set_dependents('vxlan', conf, interface)
+ # When using Wireless member interfaces we need to inform hostapd
+ # to properly set-up the bridge
+ elif interface.startswith('wlan') and interface_exists(interface):
+ set_dependents('wlan', conf, interface)
if dict_search('member.interface', bridge) is not None:
for interface in list(bridge['member']['interface']):
@@ -91,6 +102,10 @@ def get_config(config=None):
# is already live on the system - this must not be done on first commit
if interface.startswith('vxlan') and interface_exists(interface):
set_dependents('vxlan', conf, interface)
+ # When using Wireless member interfaces we need to inform hostapd
+ # to properly set-up the bridge
+ elif interface.startswith('wlan') and interface_exists(interface):
+ set_dependents('wlan', conf, interface)
# delete empty dictionary keys - no need to run code paths if nothing is there to do
if 'member' in bridge:
@@ -140,9 +155,6 @@ def verify(bridge):
if 'enable_vlan' in bridge:
if 'has_vlan' in interface_config:
raise ConfigError(error_msg + 'it has VLAN subinterface(s) assigned!')
-
- if 'wlan' in interface:
- raise ConfigError(error_msg + 'VLAN aware cannot be set!')
else:
for option in ['allowed_vlan', 'native_vlan']:
if option in interface_config:
@@ -168,12 +180,19 @@ def apply(bridge):
else:
br.update(bridge)
- for interface in dict_search('member.interface', bridge) or []:
- if interface.startswith('vxlan') and interface_exists(interface):
+ tmp = []
+ if 'member' in bridge:
+ if 'interface_remove' in bridge['member']:
+ tmp.extend(bridge['member']['interface_remove'])
+ if 'interface' in bridge['member']:
+ tmp.extend(bridge['member']['interface'])
+
+ for interface in tmp:
+ if interface.startswith(tuple(['vxlan', 'wlan'])) and interface_exists(interface):
try:
call_dependents()
except ConfigError:
- raise ConfigError('Error in updating VXLAN interface after changing bridge!')
+ raise ConfigError('Error updating member interface configuration after changing bridge!')
return None
diff --git a/src/conf_mode/interfaces_ethernet.py b/src/conf_mode/interfaces_ethernet.py
index 6da7e6a69..54d0669cb 100755
--- a/src/conf_mode/interfaces_ethernet.py
+++ b/src/conf_mode/interfaces_ethernet.py
@@ -41,6 +41,7 @@ from vyos.pki import encode_certificate
from vyos.pki import load_certificate
from vyos.pki import wrap_private_key
from vyos.template import render
+from vyos.template import render_to_string
from vyos.utils.process import call
from vyos.utils.dict import dict_search
from vyos.utils.dict import dict_to_paths_values
@@ -48,6 +49,7 @@ from vyos.utils.dict import dict_set
from vyos.utils.dict import dict_delete
from vyos.utils.file import write_file
from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
@@ -389,6 +391,10 @@ def generate(ethernet):
write_file(ca_cert_file_path, '\n'.join(ca_chains))
+ ethernet['frr_zebra_config'] = ''
+ if 'deleted' not in ethernet:
+ ethernet['frr_zebra_config'] = render_to_string('frr/evpn.mh.frr.j2', ethernet)
+
return None
def apply(ethernet):
@@ -407,6 +413,17 @@ def apply(ethernet):
call(f'systemctl {eapol_action} wpa_supplicant-wired@{ifname}')
+ zebra_daemon = 'zebra'
+ # Save original configuration prior to starting any commit actions
+ frr_cfg = frr.FRRConfig()
+
+ # The route-map used for the FIB (zebra) is part of the zebra daemon
+ frr_cfg.load_configuration(zebra_daemon)
+ frr_cfg.modify_section(f'^interface {ifname}', stop_pattern='^exit', remove_stop_mark=True)
+ if 'frr_zebra_config' in ethernet:
+ frr_cfg.add_before(frr.default_add_before, ethernet['frr_zebra_config'])
+ frr_cfg.commit_configuration(zebra_daemon)
+
if __name__ == '__main__':
try:
c = get_config()
diff --git a/src/conf_mode/load-balancing_reverse-proxy.py b/src/conf_mode/load-balancing_reverse-proxy.py
index 1569d8d71..a4efb1cd8 100755
--- a/src/conf_mode/load-balancing_reverse-proxy.py
+++ b/src/conf_mode/load-balancing_reverse-proxy.py
@@ -88,6 +88,12 @@ def verify(lb):
if {'send_proxy', 'send_proxy_v2'} <= set(bk_server_conf):
raise ConfigError(f'Cannot use both "send-proxy" and "send-proxy-v2" for server "{bk_server}"')
+ # Check if http-response-headers are configured in any frontend/backend where mode != http
+ for group in ['service', 'backend']:
+ for config_name, config in lb[group].items():
+ if 'http_response_headers' in config and ('mode' not in config or config['mode'] != 'http'):
+ raise ConfigError(f'{group} {config_name} must be set to http mode to use http_response_headers!')
+
if 'ssl' in back_config:
if {'no_verify', 'ca_certificate'} <= set(back_config['ssl']):
raise ConfigError(f'backend {back} cannot have both ssl options no-verify and ca-certificate set!')
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 4cd9b570d..f74bb217e 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -17,7 +17,6 @@
import os
from sys import exit
-from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
@@ -30,6 +29,7 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.utils.network import is_addr_assigned
+from vyos.utils.network import interface_exists
from vyos import ConfigError
from vyos import airbag
@@ -149,8 +149,12 @@ def verify(nat):
if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"')
elif 'name' in config['outbound_interface']:
- if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
- Warning(f'NAT interface "{config["outbound_interface"]["name"]}" for source NAT rule "{rule}" does not exist!')
+ interface_name = config['outbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for source NAT rule "{rule}" does not exist!')
else:
group_name = config['outbound_interface']['group']
if group_name[0] == '!':
@@ -182,8 +186,12 @@ def verify(nat):
if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"')
elif 'name' in config['inbound_interface']:
- if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
- Warning(f'NAT interface "{config["inbound_interface"]["name"]}" for destination NAT rule "{rule}" does not exist!')
+ interface_name = config['inbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for destination NAT rule "{rule}" does not exist!')
else:
group_name = config['inbound_interface']['group']
if group_name[0] == '!':
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index fe017527d..075738dad 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -17,15 +17,15 @@
import os
from sys import exit
-from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
-from vyos.utils.process import cmd
-from vyos.utils.kernel import check_kmod
from vyos.utils.dict import dict_search
+from vyos.utils.kernel import check_kmod
+from vyos.utils.network import interface_exists
+from vyos.utils.process import cmd
from vyos.template import is_ipv6
from vyos import ConfigError
from vyos import airbag
@@ -64,8 +64,12 @@ def verify(nat):
if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"')
elif 'name' in config['outbound_interface']:
- if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
- Warning(f'NAT66 interface "{config["outbound_interface"]["name"]}" for source NAT66 rule "{rule}" does not exist!')
+ interface_name = config['outbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for source NAT66 rule "{rule}" does not exist!')
addr = dict_search('translation.address', config)
if addr != None:
@@ -88,8 +92,12 @@ def verify(nat):
if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"')
elif 'name' in config['inbound_interface']:
- if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
- Warning(f'NAT66 interface "{config["inbound_interface"]["name"]}" for destination NAT66 rule "{rule}" does not exist!')
+ interface_name = config['inbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!')
return None
diff --git a/src/conf_mode/nat_cgnat.py b/src/conf_mode/nat_cgnat.py
index f41d66c66..5ad65de80 100755
--- a/src/conf_mode/nat_cgnat.py
+++ b/src/conf_mode/nat_cgnat.py
@@ -189,11 +189,6 @@ def verify(config):
if 'rule' not in config:
raise ConfigError(f'Rule must be defined!')
- # As PoC allow only one rule for CGNAT translations
- # one internal pool and one external pool
- if len(config['rule']) > 1:
- raise ConfigError(f'Only one rule is allowed for translations!')
-
for pool in ('external', 'internal'):
if pool not in config['pool']:
raise ConfigError(f'{pool} pool must be defined!')
@@ -203,6 +198,13 @@ def verify(config):
f'Range for "{pool} pool {pool_name}" must be defined!'
)
+ external_pools_query = "keys(pool.external)"
+ external_pools: list = jmespath.search(external_pools_query, config)
+ internal_pools_query = "keys(pool.internal)"
+ internal_pools: list = jmespath.search(internal_pools_query, config)
+
+ used_external_pools = {}
+ used_internal_pools = {}
for rule, rule_config in config['rule'].items():
if 'source' not in rule_config:
raise ConfigError(f'Rule "{rule}" source pool must be defined!')
@@ -212,49 +214,82 @@ def verify(config):
if 'translation' not in rule_config:
raise ConfigError(f'Rule "{rule}" translation pool must be defined!')
+ # Check if pool exists
+ internal_pool = rule_config['source']['pool']
+ if internal_pool not in internal_pools:
+ raise ConfigError(f'Internal pool "{internal_pool}" does not exist!')
+ external_pool = rule_config['translation']['pool']
+ if external_pool not in external_pools:
+ raise ConfigError(f'External pool "{external_pool}" does not exist!')
+
+ # Check pool duplication in different rules
+ if external_pool in used_external_pools:
+ raise ConfigError(
+ f'External pool "{external_pool}" is already used in rule '
+ f'{used_external_pools[external_pool]} and cannot be used in '
+ f'rule {rule}!'
+ )
+
+ if internal_pool in used_internal_pools:
+ raise ConfigError(
+ f'Internal pool "{internal_pool}" is already used in rule '
+ f'{used_internal_pools[internal_pool]} and cannot be used in '
+ f'rule {rule}!'
+ )
+
+ used_external_pools[external_pool] = rule
+ used_internal_pools[internal_pool] = rule
+
def generate(config):
if not config:
return None
- # first external pool as we allow only one as PoC
- ext_pool_name = jmespath.search("rule.*.translation | [0]", config).get('pool')
- int_pool_name = jmespath.search("rule.*.source | [0]", config).get('pool')
- ext_query = f"pool.external.{ext_pool_name}.range | keys(@)"
- int_query = f"pool.internal.{int_pool_name}.range"
- external_ranges = jmespath.search(ext_query, config)
- internal_ranges = [jmespath.search(int_query, config)]
-
- external_list_count = []
- external_list_hosts = []
- internal_list_count = []
- internal_list_hosts = []
- for ext_range in external_ranges:
- # External hosts count
- e_count = IPOperations(ext_range).get_ips_count()
- external_list_count.append(e_count)
- # External hosts list
- e_hosts = IPOperations(ext_range).convert_prefix_to_list_ips()
- external_list_hosts.extend(e_hosts)
- for int_range in internal_ranges:
- # Internal hosts count
- i_count = IPOperations(int_range).get_ips_count()
- internal_list_count.append(i_count)
- # Internal hosts list
- i_hosts = IPOperations(int_range).convert_prefix_to_list_ips()
- internal_list_hosts.extend(i_hosts)
-
- external_host_count = sum(external_list_count)
- internal_host_count = sum(internal_list_count)
- ports_per_user = int(
- jmespath.search(f'pool.external.{ext_pool_name}.per_user_limit.port', config)
- )
- external_port_range: str = jmespath.search(
- f'pool.external.{ext_pool_name}.external_port_range', config
- )
- proto_maps, other_maps = generate_port_rules(
- external_list_hosts, internal_list_hosts, ports_per_user, external_port_range
- )
+ proto_maps = []
+ other_maps = []
+
+ for rule, rule_config in config['rule'].items():
+ ext_pool_name: str = rule_config['translation']['pool']
+ int_pool_name: str = rule_config['source']['pool']
+
+ external_ranges: list = [range for range in config['pool']['external'][ext_pool_name]['range']]
+ internal_ranges: list = [range for range in config['pool']['internal'][int_pool_name]['range']]
+ external_list_hosts_count = []
+ external_list_hosts = []
+ internal_list_hosts_count = []
+ internal_list_hosts = []
+
+ for ext_range in external_ranges:
+ # External hosts count
+ e_count = IPOperations(ext_range).get_ips_count()
+ external_list_hosts_count.append(e_count)
+ # External hosts list
+ e_hosts = IPOperations(ext_range).convert_prefix_to_list_ips()
+ external_list_hosts.extend(e_hosts)
+
+ for int_range in internal_ranges:
+ # Internal hosts count
+ i_count = IPOperations(int_range).get_ips_count()
+ internal_list_hosts_count.append(i_count)
+ # Internal hosts list
+ i_hosts = IPOperations(int_range).convert_prefix_to_list_ips()
+ internal_list_hosts.extend(i_hosts)
+
+ external_host_count = sum(external_list_hosts_count)
+ internal_host_count = sum(internal_list_hosts_count)
+ ports_per_user = int(
+ jmespath.search(f'pool.external."{ext_pool_name}".per_user_limit.port', config)
+ )
+ external_port_range: str = jmespath.search(
+ f'pool.external."{ext_pool_name}".external_port_range', config
+ )
+
+ rule_proto_maps, rule_other_maps = generate_port_rules(
+ external_list_hosts, internal_list_hosts, ports_per_user, external_port_range
+ )
+
+ proto_maps.extend(rule_proto_maps)
+ other_maps.extend(rule_other_maps)
config['proto_map_elements'] = ', '.join(proto_maps)
config['other_map_elements'] = ', '.join(other_maps)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 4df97d133..22f020099 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -31,6 +31,7 @@ from vyos.utils.dict import dict_search
from vyos.utils.network import get_interface_vrf
from vyos.utils.network import is_addr_assigned
from vyos.utils.process import process_named_running
+from vyos.utils.process import call
from vyos import ConfigError
from vyos import frr
from vyos import airbag
@@ -50,13 +51,8 @@ def get_config(config=None):
# eqivalent of the C foo ? 'a' : 'b' statement
base = vrf and ['vrf', 'name', vrf, 'protocols', 'bgp'] or base_path
- bgp = conf.get_config_dict(
- base,
- key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True,
- with_recursive_defaults=True,
- )
+ bgp = conf.get_config_dict(base, key_mangling=('-', '_'),
+ get_first_key=True, no_tag_node_value_mangle=True)
bgp['dependent_vrfs'] = conf.get_config_dict(['vrf', 'name'],
key_mangling=('-', '_'),
@@ -75,22 +71,29 @@ def get_config(config=None):
if vrf:
bgp.update({'vrf' : vrf})
# We can not delete the BGP VRF instance if there is a L3VNI configured
+ # FRR L3VNI must be deleted first otherwise we will see error:
+ # "FRR error: Please unconfigure l3vni 3000"
tmp = ['vrf', 'name', vrf, 'vni']
- if conf.exists(tmp):
- bgp.update({'vni' : conf.return_value(tmp)})
+ if conf.exists_effective(tmp):
+ bgp.update({'vni' : conf.return_effective_value(tmp)})
# We can safely delete ourself from the dependent vrf list
if vrf in bgp['dependent_vrfs']:
del bgp['dependent_vrfs'][vrf]
- bgp['dependent_vrfs'].update({'default': {'protocols': {
- 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'),
- get_first_key=True,
- no_tag_node_value_mangle=True)}}})
+ bgp['dependent_vrfs'].update({'default': {'protocols': {
+ 'bgp': conf.get_config_dict(base_path, key_mangling=('-', '_'),
+ get_first_key=True,
+ no_tag_node_value_mangle=True)}}})
+
if not conf.exists(base):
# If bgp instance is deleted then mark it
bgp.update({'deleted' : ''})
return bgp
+ # We have gathered the dict representation of the CLI, but there are default
+ # options which we need to update into the dictionary retrived.
+ bgp = conf.merge_defaults(bgp, recursive=True)
+
# We also need some additional information from the config, prefix-lists
# and route-maps for instance. They will be used in verify().
#
@@ -242,10 +245,6 @@ def verify(bgp):
if verify_vrf_as_import(bgp['vrf'], tmp_afi, bgp['dependent_vrfs']):
raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
'unconfigure "import vrf" commands!')
- # We can not delete the BGP instance if a L3VNI instance exists
- if 'vni' in bgp:
- raise ConfigError(f'Cannot delete VRF instance "{bgp["vrf"]}", ' \
- f'unconfigure VNI "{bgp["vni"]}" first!')
else:
# We are running in the default VRF context, thus we can not delete
# our main BGP instance if there are dependent BGP VRF instances.
@@ -254,7 +253,11 @@ def verify(bgp):
if vrf != 'default':
if dict_search('protocols.bgp', vrf_options):
raise ConfigError('Cannot delete default BGP instance, ' \
- 'dependent VRF instance(s) exist!')
+ 'dependent VRF instance(s) exist(s)!')
+ if 'vni' in vrf_options:
+ raise ConfigError('Cannot delete default BGP instance, ' \
+ 'dependent L3VNI exists!')
+
return None
if 'system_as' not in bgp:
@@ -330,7 +333,7 @@ def verify(bgp):
raise ConfigError('Cannot have local-as same as system-as number')
# Neighbor AS specified for local-as and remote-as can not be the same
- if dict_search('remote_as', peer_config) == asn:
+ if dict_search('remote_as', peer_config) == asn and neighbor != 'peer_group':
raise ConfigError(f'Neighbor "{peer}" has local-as specified which is '\
'the same as remote-as, this is not allowed!')
@@ -607,6 +610,13 @@ def generate(bgp):
return None
def apply(bgp):
+ if 'deleted' in bgp:
+ # We need to ensure that the L3VNI is deleted first.
+ # This is not possible with old config backend
+ # priority bug
+ if {'vrf', 'vni'} <= set(bgp):
+ call('vtysh -c "conf t" -c "vrf {vrf}" -c "no vni {vni}"'.format(**bgp))
+
bgp_daemon = 'bgpd'
# Save original configuration prior to starting any commit actions
diff --git a/src/conf_mode/qos.py b/src/conf_mode/qos.py
index ccfc8f6b8..8a590cbc6 100755
--- a/src/conf_mode/qos.py
+++ b/src/conf_mode/qos.py
@@ -39,6 +39,9 @@ from vyos.utils.dict import dict_search_recursive
from vyos.utils.process import run
from vyos import ConfigError
from vyos import airbag
+from vyos.xml_ref import relative_defaults
+
+
airbag.enable()
map_vyops_tc = {
@@ -115,8 +118,18 @@ def get_config(config=None):
for rd_name in list(qos['policy'][policy]):
# There are eight precedence levels - ensure all are present
# to be filled later down with the appropriate default values
- default_precedence = {'precedence' : { '0' : {}, '1' : {}, '2' : {}, '3' : {},
- '4' : {}, '5' : {}, '6' : {}, '7' : {} }}
+ default_p_val = relative_defaults(
+ ['qos', 'policy', 'random-detect', rd_name, 'precedence'],
+ {'precedence': {'0': {}}},
+ get_first_key=True, recursive=True
+ )['0']
+ default_p_val = {key.replace('-', '_'): value for key, value in default_p_val.items()}
+ default_precedence = {
+ 'precedence': {'0': default_p_val, '1': default_p_val,
+ '2': default_p_val, '3': default_p_val,
+ '4': default_p_val, '5': default_p_val,
+ '6': default_p_val, '7': default_p_val}}
+
qos['policy']['random_detect'][rd_name] = dict_merge(
default_precedence, qos['policy']['random_detect'][rd_name])
@@ -124,18 +137,6 @@ def get_config(config=None):
for policy in qos.get('policy', []):
for p_name, p_config in qos['policy'][policy].items():
- if 'precedence' in p_config:
- # precedence settings are a bit more complex as they are
- # calculated under specific circumstances:
- for precedence in p_config['precedence']:
- max_thr = int(qos['policy'][policy][p_name]['precedence'][precedence]['maximum_threshold'])
- if 'minimum_threshold' not in qos['policy'][policy][p_name]['precedence'][precedence]:
- qos['policy'][policy][p_name]['precedence'][precedence]['minimum_threshold'] = str(
- int((9 + int(precedence)) * max_thr) // 18);
-
- if 'queue_limit' not in qos['policy'][policy][p_name]['precedence'][precedence]:
- qos['policy'][policy][p_name]['precedence'][precedence]['queue_limit'] = \
- str(int(4 * max_thr))
# cleanup empty match config
if 'class' in p_config:
for cls, cls_config in p_config['class'].items():
diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py
index c7333dd3a..7af88007c 100755
--- a/src/conf_mode/service_dhcpv6-server.py
+++ b/src/conf_mode/service_dhcpv6-server.py
@@ -106,14 +106,14 @@ def verify(dhcpv6):
# Stop address must be greater or equal to start address
if not ip_address(stop) >= ip_address(start):
- raise ConfigError(f'Range stop address "{stop}" must be greater then or equal ' \
+ raise ConfigError(f'Range stop address "{stop}" must be greater than or equal ' \
f'to the range start address "{start}"!')
# DHCPv6 range start address must be unique - two ranges can't
# start with the same address - makes no sense
if start in range6_start:
raise ConfigError(f'Conflicting DHCPv6 lease range: '\
- f'Pool start address "{start}" defined multipe times!')
+ f'Pool start address "{start}" defined multiple times!')
range6_start.append(start)
@@ -121,7 +121,7 @@ def verify(dhcpv6):
# end with the same address - makes no sense
if stop in range6_stop:
raise ConfigError(f'Conflicting DHCPv6 lease range: '\
- f'Pool stop address "{stop}" defined multipe times!')
+ f'Pool stop address "{stop}" defined multiple times!')
range6_stop.append(stop)
@@ -180,7 +180,7 @@ def verify(dhcpv6):
if 'option' in subnet_config:
if 'vendor_option' in subnet_config['option']:
if len(dict_search('option.vendor_option.cisco.tftp_server', subnet_config)) > 2:
- raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!')
+ raise ConfigError(f'No more than two Cisco tftp-servers should be defined for subnet "{subnet}"!')
# Subnets must be unique
if subnet in subnets:
diff --git a/src/conf_mode/service_pppoe-server.py b/src/conf_mode/service_pppoe-server.py
index 328487985..c95f976d3 100755
--- a/src/conf_mode/service_pppoe-server.py
+++ b/src/conf_mode/service_pppoe-server.py
@@ -84,12 +84,29 @@ def verify_pado_delay(pppoe):
pado_delay = pppoe['pado_delay']
delays_without_sessions = pado_delay['delays_without_sessions']
+ if 'disable' in delays_without_sessions:
+ raise ConfigError(
+ 'Number of sessions must be specified for "pado-delay disable"'
+ )
+
if len(delays_without_sessions) > 1:
raise ConfigError(
f'Cannot add more then ONE pado-delay without sessions, '
f'but {len(delays_without_sessions)} were set'
)
+ if 'disable' in [delay[0] for delay in pado_delay['delays_with_sessions']]:
+ # need to sort delays by sessions to verify if there is no delay
+ # for sessions after disabling
+ sorted_pado_delay = sorted(pado_delay['delays_with_sessions'], key=lambda k_v: k_v[1])
+ last_delay = sorted_pado_delay[-1]
+
+ if last_delay[0] != 'disable':
+ raise ConfigError(
+ f'Cannot add pado-delay after disabled sessions, but '
+ f'"pado-delay {last_delay[0]} sessions {last_delay[1]}" was set'
+ )
+
def verify(pppoe):
if not pppoe:
return None
diff --git a/src/conf_mode/service_upnp.py b/src/conf_mode/service_upnp.py
deleted file mode 100755
index 0df8dc09e..000000000
--- a/src/conf_mode/service_upnp.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021-2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-
-from sys import exit
-import uuid
-import netifaces
-from ipaddress import IPv4Network
-from ipaddress import IPv6Network
-
-from vyos.config import Config
-from vyos.utils.process import call
-from vyos.template import render
-from vyos.template import is_ipv4
-from vyos.template import is_ipv6
-from vyos import ConfigError
-from vyos import airbag
-airbag.enable()
-
-config_file = r'/run/upnp/miniupnp.conf'
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- base = ['service', 'upnp']
- upnpd = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
-
- if not upnpd:
- return None
-
- upnpd = conf.merge_defaults(upnpd, recursive=True)
-
- uuidgen = uuid.uuid1()
- upnpd.update({'uuid': uuidgen})
-
- return upnpd
-
-def get_all_interface_addr(prefix, filter_dev, filter_family):
- list_addr = []
- for interface in netifaces.interfaces():
- if filter_dev and interface in filter_dev:
- continue
- addrs = netifaces.ifaddresses(interface)
- if netifaces.AF_INET in addrs.keys():
- if netifaces.AF_INET in filter_family:
- for addr in addrs[netifaces.AF_INET]:
- if prefix:
- # we need to manually assemble a list of IPv4 address/prefix
- prefix = '/' + \
- str(IPv4Network('0.0.0.0/' + addr['netmask']).prefixlen)
- list_addr.append(addr['addr'] + prefix)
- else:
- list_addr.append(addr['addr'])
- if netifaces.AF_INET6 in addrs.keys():
- if netifaces.AF_INET6 in filter_family:
- for addr in addrs[netifaces.AF_INET6]:
- if prefix:
- # we need to manually assemble a list of IPv4 address/prefix
- bits = bin(int(addr['netmask'].replace(':', '').split('/')[0], 16)).count('1')
- prefix = '/' + str(bits)
- list_addr.append(addr['addr'] + prefix)
- else:
- list_addr.append(addr['addr'])
-
- return list_addr
-
-def verify(upnpd):
- if not upnpd:
- return None
-
- if 'wan_interface' not in upnpd:
- raise ConfigError('To enable UPNP, you must have the "wan-interface" option!')
-
- if 'rule' in upnpd:
- for rule, rule_config in upnpd['rule'].items():
- for option in ['external_port_range', 'internal_port_range', 'ip', 'action']:
- if option not in rule_config:
- tmp = option.replace('_', '-')
- raise ConfigError(f'Every UPNP rule requires "{tmp}" to be set!')
-
- if 'stun' in upnpd:
- for option in ['host', 'port']:
- if option not in upnpd['stun']:
- raise ConfigError(f'A UPNP stun support must have an "{option}" option!')
-
- # Check the validity of the IP address
- listen_dev = []
- system_addrs_cidr = get_all_interface_addr(True, [], [netifaces.AF_INET, netifaces.AF_INET6])
- system_addrs = get_all_interface_addr(False, [], [netifaces.AF_INET, netifaces.AF_INET6])
- if 'listen' not in upnpd:
- raise ConfigError(f'Listen address or interface is required!')
- for listen_if_or_addr in upnpd['listen']:
- if listen_if_or_addr not in netifaces.interfaces():
- listen_dev.append(listen_if_or_addr)
- if (listen_if_or_addr not in system_addrs) and (listen_if_or_addr not in system_addrs_cidr) and \
- (listen_if_or_addr not in netifaces.interfaces()):
- if is_ipv4(listen_if_or_addr) and IPv4Network(listen_if_or_addr).is_multicast:
- raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed'
- f'to listen on. It is not an interface address nor a multicast address!')
- if is_ipv6(listen_if_or_addr) and IPv6Network(listen_if_or_addr).is_multicast:
- raise ConfigError(f'The address "{listen_if_or_addr}" is an address that is not allowed'
- f'to listen on. It is not an interface address nor a multicast address!')
-
- system_listening_dev_addrs_cidr = get_all_interface_addr(True, listen_dev, [netifaces.AF_INET6])
- system_listening_dev_addrs = get_all_interface_addr(False, listen_dev, [netifaces.AF_INET6])
- for listen_if_or_addr in upnpd['listen']:
- if listen_if_or_addr not in netifaces.interfaces() and \
- (listen_if_or_addr not in system_listening_dev_addrs_cidr) and \
- (listen_if_or_addr not in system_listening_dev_addrs) and \
- is_ipv6(listen_if_or_addr) and \
- (not IPv6Network(listen_if_or_addr).is_multicast):
- raise ConfigError(f'{listen_if_or_addr} must listen on the interface of the network card')
-
-def generate(upnpd):
- if not upnpd:
- return None
-
- if os.path.isfile(config_file):
- os.unlink(config_file)
-
- render(config_file, 'firewall/upnpd.conf.j2', upnpd)
-
-def apply(upnpd):
- systemd_service_name = 'miniupnpd.service'
- if not upnpd:
- # Stop the UPNP service
- call(f'systemctl stop {systemd_service_name}')
- else:
- # Start the UPNP service
- call(f'systemctl restart {systemd_service_name}')
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/conf_mode/system_host-name.py b/src/conf_mode/system_host-name.py
index 8975cadb6..3f245f166 100755
--- a/src/conf_mode/system_host-name.py
+++ b/src/conf_mode/system_host-name.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2018-2023 VyOS maintainers and contributors
+# Copyright (C) 2018-2024 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -22,6 +22,7 @@ import vyos.hostsd_client
from vyos.base import Warning
from vyos.config import Config
+from vyos.configdict import leaf_node_changed
from vyos.ifconfig import Section
from vyos.template import is_ip
from vyos.utils.process import cmd
@@ -37,6 +38,7 @@ default_config_data = {
'domain_search': [],
'nameserver': [],
'nameservers_dhcp_interfaces': {},
+ 'snmpd_restart_reqired': False,
'static_host_mapping': {}
}
@@ -52,6 +54,10 @@ def get_config(config=None):
hosts['hostname'] = conf.return_value(['system', 'host-name'])
+ base = ['system']
+ if leaf_node_changed(conf, base + ['host-name']) or leaf_node_changed(conf, base + ['domain-name']):
+ hosts['snmpd_restart_reqired'] = True
+
# This may happen if the config is not loaded yet,
# e.g. if run by cloud-init
if not hosts['hostname']:
@@ -171,7 +177,7 @@ def apply(config):
call("systemctl restart rsyslog.service")
# If SNMP is running, restart it too
- if process_named_running('snmpd'):
+ if process_named_running('snmpd') and config['snmpd_restart_reqired']:
call('systemctl restart snmpd.service')
return None
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 587309005..8d8c234c0 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -130,11 +130,6 @@ def get_config(config=None):
tmp = {'policy' : {'route-map' : conf.get_config_dict(['policy', 'route-map'],
get_first_key=True)}}
- # L3VNI setup is done via vrf_vni.py as it must be de-configured (on node
- # deletetion prior to the BGP process. Tell the Jinja2 template no VNI
- # setup is needed
- vrf.update({'no_vni' : ''})
-
# Merge policy dict into "regular" config dict
vrf = dict_merge(tmp, vrf)
return vrf
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
deleted file mode 100644
index 8dab164d7..000000000
--- a/src/conf_mode/vrf_vni.py
+++ /dev/null
@@ -1,103 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2023-2024 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-from sys import argv
-from sys import exit
-
-from vyos.config import Config
-from vyos.template import render_to_string
-from vyos import ConfigError
-from vyos import frr
-from vyos import airbag
-airbag.enable()
-
-def get_config(config=None):
- if config:
- conf = config
- else:
- conf = Config()
-
- vrf_name = None
- if len(argv) > 1:
- vrf_name = argv[1]
- else:
- return None
-
- # Using duplicate L3VNIs makes no sense - it's also forbidden in FRR,
- # thus VyOS CLI must deny this, too. Instead of getting only the dict for
- # the requested VRF and den comparing it with depenent VRfs to not have any
- # duplicate we will just grad ALL VRFs by default but only render/apply
- # the configuration for the requested VRF - that makes the code easier and
- # hopefully less error prone
- vrf = conf.get_config_dict(['vrf'], key_mangling=('-', '_'),
- no_tag_node_value_mangle=True,
- get_first_key=True)
-
- # Store name of VRF we are interested in for FRR config rendering
- vrf.update({'only_vrf' : vrf_name})
-
- return vrf
-
-def verify(vrf):
- if not vrf:
- return
-
- if len(argv) < 2:
- raise ConfigError('VRF parameter not specified when valling vrf_vni.py')
-
- if 'name' in vrf:
- vni_ids = []
- for name, vrf_config in vrf['name'].items():
- # VRF VNI (Virtual Network Identifier) must be unique on the system
- if 'vni' in vrf_config:
- if vrf_config['vni'] in vni_ids:
- raise ConfigError(f'VRF "{name}" VNI is not unique!')
- vni_ids.append(vrf_config['vni'])
-
- return None
-
-def generate(vrf):
- if not vrf:
- return
-
- vrf['new_frr_config'] = render_to_string('frr/zebra.vrf.route-map.frr.j2', vrf)
- return None
-
-def apply(vrf):
- frr_daemon = 'zebra'
-
- # add configuration to FRR
- frr_cfg = frr.FRRConfig()
- frr_cfg.load_configuration(frr_daemon)
- # There is only one VRF inside the dict as we read only one in get_config()
- if vrf and 'only_vrf' in vrf:
- vrf_name = vrf['only_vrf']
- frr_cfg.modify_section(f'^vrf {vrf_name}', stop_pattern='^exit-vrf', remove_stop_mark=True)
- if vrf and 'new_frr_config' in vrf:
- frr_cfg.add_before(frr.default_add_before, vrf['new_frr_config'])
- frr_cfg.commit_configuration(frr_daemon)
-
- return None
-
-if __name__ == '__main__':
- try:
- c = get_config()
- verify(c)
- generate(c)
- apply(c)
- except ConfigError as e:
- print(e)
- exit(1)
diff --git a/src/helpers/vyos_config_sync.py b/src/helpers/vyos_config_sync.py
index 0604b2837..9d9aec376 100755
--- a/src/helpers/vyos_config_sync.py
+++ b/src/helpers/vyos_config_sync.py
@@ -93,7 +93,8 @@ def set_remote_config(
key: str,
op: str,
mask: Dict[str, Any],
- config: Dict[str, Any]) -> Optional[Dict[str, Any]]:
+ config: Dict[str, Any],
+ port: int) -> Optional[Dict[str, Any]]:
"""Loads the VyOS configuration in JSON format to a remote host.
Args:
@@ -102,6 +103,7 @@ def set_remote_config(
op (str): The operation to perform (set or load).
mask (dict): The dict of paths in sections.
config (dict): The dict of masked config data.
+ port (int): The remote API port
Returns:
Optional[Dict[str, Any]]: The response from the remote host as a
@@ -113,7 +115,7 @@ def set_remote_config(
# Disable the InsecureRequestWarning
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
- url = f'https://{address}/configure-section'
+ url = f'https://{address}:{port}/configure-section'
data = json.dumps({
'op': op,
'mask': mask,
@@ -138,7 +140,8 @@ def is_section_revised(section: List[str]) -> bool:
def config_sync(secondary_address: str,
secondary_key: str,
sections: List[list[str]],
- mode: str):
+ mode: str,
+ secondary_port: int):
"""Retrieve a config section from primary router in JSON format and send it to
secondary router
"""
@@ -158,7 +161,8 @@ def config_sync(secondary_address: str,
key=secondary_key,
op=mode,
mask=mask_dict,
- config=config_dict)
+ config=config_dict,
+ port=secondary_port)
logger.debug(f"Set config for sections '{sections}': {set_config}")
@@ -178,14 +182,12 @@ if __name__ == '__main__':
secondary_address = config.get('secondary', {}).get('address')
secondary_address = bracketize_ipv6(secondary_address)
secondary_key = config.get('secondary', {}).get('key')
+ secondary_port = int(config.get('secondary', {}).get('port', 443))
sections = config.get('section')
timeout = int(config.get('secondary', {}).get('timeout'))
- if not all([
- mode, secondary_address, secondary_key, sections
- ]):
- logger.error(
- "Missing required configuration data for config synchronization.")
+ if not all([mode, secondary_address, secondary_key, sections]):
+ logger.error("Missing required configuration data for config synchronization.")
exit(0)
# Generate list_sections of sections/subsections
@@ -200,5 +202,4 @@ if __name__ == '__main__':
else:
list_sections.append([section])
- config_sync(secondary_address, secondary_key,
- list_sections, mode)
+ config_sync(secondary_address, secondary_key, list_sections, mode, secondary_port)
diff --git a/src/migration-scripts/nat/7-to-8 b/src/migration-scripts/nat/7-to-8
new file mode 100755
index 000000000..ab2ffa6d3
--- /dev/null
+++ b/src/migration-scripts/nat/7-to-8
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# T6345: random - In kernel 5.0 and newer this is the same as fully-random.
+# In earlier kernels the port mapping will be randomized using a seeded
+# MD5 hash mix using source and destination address and destination port.
+# drop fully-random from CLI
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+
+for direction in ['source', 'destination']:
+ # If a node doesn't exist, we obviously have nothing to do.
+ if not config.exists(['nat', direction]):
+ continue
+
+ # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist,
+ # but there are no rules under it.
+ if not config.list_nodes(['nat', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat', direction, 'rule']):
+ port_mapping = ['nat', direction, 'rule', rule, 'translation', 'options', 'port-mapping']
+ if config.exists(port_mapping):
+ tmp = config.return_value(port_mapping)
+ if tmp == 'fully-random':
+ config.set(port_mapping, value='random')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/migration-scripts/pppoe-server/9-to-10 b/src/migration-scripts/pppoe-server/9-to-10
new file mode 100755
index 000000000..e0c782f04
--- /dev/null
+++ b/src/migration-scripts/pppoe-server/9-to-10
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Migration of pado-delay options
+
+from sys import argv
+from sys import exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+base = ['service', 'pppoe-server', 'pado-delay']
+if not config.exists(base):
+ exit(0)
+
+pado_delay = {}
+for delay in config.list_nodes(base):
+ sessions = config.return_value(base + [delay, 'sessions'])
+ pado_delay[delay] = sessions
+
+# need to define delay for latest sessions
+sorted_delays = dict(sorted(pado_delay.items(), key=lambda k_v: int(k_v[1])))
+last_delay = list(sorted_delays)[-1]
+
+# Rename last delay -> disable
+tmp = base + [last_delay]
+if config.exists(tmp):
+ config.rename(tmp, 'disable')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ exit(1)
diff --git a/src/op_mode/bonding.py b/src/op_mode/bonding.py
new file mode 100755
index 000000000..07bccbd4b
--- /dev/null
+++ b/src/op_mode/bonding.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016-2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This script will parse 'sudo cat /proc/net/bonding/<interface name>' and return table output for lacp related info
+
+import subprocess
+import re
+import sys
+import typing
+from tabulate import tabulate
+
+import vyos.opmode
+from vyos.configquery import ConfigTreeQuery
+
+def list_to_dict(data, headers, basekey):
+ data_list = {basekey: []}
+
+ for row in data:
+ row_dict = {headers[i]: row[i] for i in range(len(headers))}
+ data_list[basekey].append(row_dict)
+
+ return data_list
+
+def show_lacp_neighbors(raw: bool, interface: typing.Optional[str]):
+ headers = ["Interface", "Member", "Local ID", "Remote ID"]
+ data = subprocess.run(f"cat /proc/net/bonding/{interface}", stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True, text=False).stdout.decode('utf-8')
+ if 'Bonding Mode: IEEE 802.3ad Dynamic link aggregation' not in data:
+ raise vyos.opmode.DataUnavailable(f"{interface} is not present or not configured with mode 802.3ad")
+
+ pattern = re.compile(
+ r"Slave Interface: (?P<member>\w+\d+).*?"
+ r"system mac address: (?P<local_id>[0-9a-f:]+).*?"
+ r"details partner lacp pdu:.*?"
+ r"system mac address: (?P<remote_id>[0-9a-f:]+)",
+ re.DOTALL
+ )
+
+ interfaces = []
+
+ for match in re.finditer(pattern, data):
+ member = match.group("member")
+ local_id = match.group("local_id")
+ remote_id = match.group("remote_id")
+ interfaces.append([interface, member, local_id, remote_id])
+
+ if raw:
+ return list_to_dict(interfaces, headers, 'lacp')
+ else:
+ return tabulate(interfaces, headers)
+
+def show_lacp_detail(raw: bool, interface: typing.Optional[str]):
+ headers = ["Interface", "Members", "Mode", "Rate", "System-MAC", "Hash"]
+ query = ConfigTreeQuery()
+
+ if interface:
+ intList = [interface]
+ else:
+ intList = query.list_nodes(['interfaces', 'bonding'])
+
+ bondList = []
+
+ for interface in intList:
+ data = subprocess.run(f"cat /proc/net/bonding/{interface}", stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True, text=False).stdout.decode('utf-8')
+ if 'Bonding Mode: IEEE 802.3ad Dynamic link aggregation' not in data:
+ continue
+
+ mode_active = "active" if "LACP active: on" in data else "passive"
+ lacp_rate = re.search(r"LACP rate: (\w+)", data).group(1) if re.search(r"LACP rate: (\w+)", data) else "N/A"
+ hash_policy = re.search(r"Transmit Hash Policy: (.+?) \(\d+\)", data).group(1) if re.search(r"Transmit Hash Policy: (.+?) \(\d+\)", data) else "N/A"
+ system_mac = re.search(r"System MAC address: ([0-9a-f:]+)", data).group(1) if re.search(r"System MAC address: ([0-9a-f:]+)", data) else "N/A"
+ if raw:
+ members = re.findall(r"Slave Interface: ([a-zA-Z0-9:_-]+)", data)
+ else:
+ members = ",".join(set(re.findall(r"Slave Interface: ([a-zA-Z0-9:_-]+)", data)))
+
+ bondList.append([interface, members, mode_active, lacp_rate, system_mac, hash_policy])
+
+ if raw:
+ return list_to_dict(bondList, headers, 'lacp')
+ else:
+ return tabulate(bondList, headers)
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/cgnat.py b/src/op_mode/cgnat.py
new file mode 100755
index 000000000..9ad8f92f9
--- /dev/null
+++ b/src/op_mode/cgnat.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import json
+import sys
+import typing
+
+from tabulate import tabulate
+
+import vyos.opmode
+
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
+
+CGNAT_TABLE = 'cgnat'
+
+
+def _get_raw_data(external_address: str = '', internal_address: str = '') -> list[dict]:
+ """Get CGNAT dictionary and filter by external or internal address if provided."""
+ cmd_output = cmd(f'nft --json list table ip {CGNAT_TABLE}')
+ data = json.loads(cmd_output)
+
+ elements = data['nftables'][2]['map']['elem']
+ allocations = []
+ for elem in elements:
+ internal = elem[0] # internal
+ external = elem[1]['concat'][0] # external
+ start_port = elem[1]['concat'][1]['range'][0]
+ end_port = elem[1]['concat'][1]['range'][1]
+ port_range = f'{start_port}-{end_port}'
+
+ if (internal_address and internal != internal_address) or (
+ external_address and external != external_address
+ ):
+ continue
+
+ allocations.append(
+ {
+ 'internal_address': internal,
+ 'external_address': external,
+ 'port_range': port_range,
+ }
+ )
+
+ return allocations
+
+
+def _get_formatted_output(allocations: list[dict]) -> str:
+ # Convert the list of dictionaries to a list of tuples for tabulate
+ headers = ['Internal IP', 'External IP', 'Port range']
+ data = [
+ (alloc['internal_address'], alloc['external_address'], alloc['port_range'])
+ for alloc in allocations
+ ]
+ output = tabulate(data, headers, numalign="left")
+ return output
+
+
+def show_allocation(
+ raw: bool,
+ external_address: typing.Optional[str],
+ internal_address: typing.Optional[str],
+) -> str:
+ config = ConfigTreeQuery()
+ if not config.exists('nat cgnat'):
+ raise vyos.opmode.UnconfiguredSubsystem('CGNAT is not configured')
+
+ if raw:
+ return _get_raw_data(external_address, internal_address)
+
+ else:
+ raw_data = _get_raw_data(external_address, internal_address)
+ return _get_formatted_output(raw_data)
+
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/evpn.py b/src/op_mode/evpn.py
new file mode 100644
index 000000000..cae4ab9f5
--- /dev/null
+++ b/src/op_mode/evpn.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2016-2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# This script is a helper to run VTYSH commands for "show evpn", allowing for the --raw flag to output JSON
+
+import sys
+import typing
+import json
+
+import vyos.opmode
+from vyos.utils.process import cmd
+
+def show_evpn(raw: bool, command: typing.Optional[str]):
+ if raw:
+ command = f"{command} json"
+ evpnDict = {}
+ try:
+ evpnDict['evpn'] = json.loads(cmd(f"vtysh -c '{command}'"))
+ except:
+ raise vyos.opmode.DataUnavailable(f"\"{command.replace(' json', '')}\" is invalid or has no JSON option")
+
+ return evpnDict
+ else:
+ return cmd(f"vtysh -c '{command}'")
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/firewall.py b/src/op_mode/firewall.py
index 442c186cc..15fbb65a2 100755
--- a/src/op_mode/firewall.py
+++ b/src/op_mode/firewall.py
@@ -531,9 +531,15 @@ def show_firewall_group(name=None):
continue
for idx, member in enumerate(members):
- val = member.get('val', 'N/D')
- timeout = str(member.get('timeout', 'N/D'))
- expires = str(member.get('expires', 'N/D'))
+ if isinstance(member, str):
+ # Only member, and no timeout:
+ val = member
+ timeout = "N/D"
+ expires = "N/D"
+ else:
+ val = member.get('val', 'N/D')
+ timeout = str(member.get('timeout', 'N/D'))
+ expires = str(member.get('expires', 'N/D'))
if args.detail:
row.append(f'{val} (timeout: {timeout}, expires: {expires})')
diff --git a/src/op_mode/image_installer.py b/src/op_mode/image_installer.py
index ba0e3b6db..0d2d7076c 100755
--- a/src/op_mode/image_installer.py
+++ b/src/op_mode/image_installer.py
@@ -23,6 +23,8 @@ from shutil import copy, chown, rmtree, copytree
from glob import glob
from sys import exit
from os import environ
+from os import readlink
+from os import getpid, getppid
from typing import Union
from urllib.parse import urlparse
from passlib.hosts import linux_context
@@ -65,7 +67,7 @@ MSG_INPUT_PASSWORD: str = 'Please enter a password for the "vyos" user:'
MSG_INPUT_PASSWORD_CONFIRM: str = 'Please confirm password for the "vyos" user:'
MSG_INPUT_ROOT_SIZE_ALL: str = 'Would you like to use all the free space on the drive?'
MSG_INPUT_ROOT_SIZE_SET: str = 'Please specify the size (in GB) of the root partition (min is 1.5 GB)?'
-MSG_INPUT_CONSOLE_TYPE: str = 'What console should be used by default? (K: KVM, S: Serial, U: USB-Serial)?'
+MSG_INPUT_CONSOLE_TYPE: str = 'What console should be used by default? (K: KVM, S: Serial)?'
MSG_INPUT_COPY_DATA: str = 'Would you like to copy data to the new image?'
MSG_INPUT_CHOOSE_COPY_DATA: str = 'From which image would you like to save config information?'
MSG_INPUT_COPY_ENC_DATA: str = 'Would you like to copy the encrypted config to the new image?'
@@ -614,6 +616,20 @@ def copy_ssh_host_keys() -> bool:
return False
+def console_hint() -> str:
+ pid = getppid() if 'SUDO_USER' in environ else getpid()
+ try:
+ path = readlink(f'/proc/{pid}/fd/1')
+ except OSError:
+ path = '/dev/tty'
+
+ name = Path(path).name
+ if name == 'ttyS0':
+ return 'S'
+ else:
+ return 'K'
+
+
def cleanup(mounts: list[str] = [], remove_items: list[str] = []) -> None:
"""Clean up after installation
@@ -709,9 +725,9 @@ def install_image() -> None:
# ask for default console
console_type: str = ask_input(MSG_INPUT_CONSOLE_TYPE,
- default='K',
- valid_responses=['K', 'S', 'U'])
- console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS', 'U': 'ttyUSB'}
+ default=console_hint(),
+ valid_responses=['K', 'S'])
+ console_dict: dict[str, str] = {'K': 'tty', 'S': 'ttyS'}
config_boot_list = ['/opt/vyatta/etc/config/config.boot',
'/opt/vyatta/etc/config.boot.default']
diff --git a/src/op_mode/image_manager.py b/src/op_mode/image_manager.py
index 1cfb5f5a1..fb4286dbc 100755
--- a/src/op_mode/image_manager.py
+++ b/src/op_mode/image_manager.py
@@ -21,7 +21,7 @@ from argparse import ArgumentParser, Namespace
from pathlib import Path
from shutil import rmtree
from sys import exit
-from typing import Optional
+from typing import Optional, Literal, TypeAlias, get_args
from vyos.system import disk, grub, image, compat
from vyos.utils.io import ask_yes_no, select_entry
@@ -33,6 +33,8 @@ DELETE_IMAGE_PROMPT_MSG: str = 'Select an image to delete:'
MSG_DELETE_IMAGE_RUNNING: str = 'Currently running image cannot be deleted; reboot into another image first'
MSG_DELETE_IMAGE_DEFAULT: str = 'Default image cannot be deleted; set another image as default first'
+ConsoleType: TypeAlias = Literal['tty', 'ttyS']
+
def annotate_list(images_list: list[str]) -> list[str]:
"""Annotate list of images with additional info
@@ -202,6 +204,15 @@ def rename_image(name_old: str, name_new: str) -> None:
exit(f'Unable to rename the encrypted config for "{name_old}" to "{name_new}": {err}')
+@compat.grub_cfg_update
+def set_console_type(console_type: ConsoleType) -> None:
+ console_choice = get_args(ConsoleType)
+ if console_type not in console_choice:
+ exit(f'console type \'{console_type}\' not available')
+
+ grub.set_console_type(console_type)
+
+
def list_images() -> None:
"""Print list of available images for CLI hints"""
images_list: list[str] = grub.version_list()
@@ -209,6 +220,13 @@ def list_images() -> None:
print(image_name)
+def list_console_types() -> None:
+ """Print list of console types for CLI hints"""
+ console_types: list[str] = list(get_args(ConsoleType))
+ for console_type in console_types:
+ print(console_type)
+
+
def parse_arguments() -> Namespace:
"""Parse arguments
@@ -217,7 +235,8 @@ def parse_arguments() -> Namespace:
"""
parser: ArgumentParser = ArgumentParser(description='Manage system images')
parser.add_argument('--action',
- choices=['delete', 'set', 'rename', 'list'],
+ choices=['delete', 'set', 'set_console_type',
+ 'rename', 'list', 'list_console_types'],
required=True,
help='action to perform with an image')
parser.add_argument('--no-prompt', action='store_true',
@@ -227,6 +246,7 @@ def parse_arguments() -> Namespace:
help=
'a name of an image to add, delete, install, rename, or set as default')
parser.add_argument('--image-new-name', help='a new name for image')
+ parser.add_argument('--console-type', help='console type for boot')
args: Namespace = parser.parse_args()
# Validate arguments
if args.action == 'rename' and (not args.image_name or
@@ -243,10 +263,14 @@ if __name__ == '__main__':
delete_image(args.image_name, args.no_prompt)
if args.action == 'set':
set_image(args.image_name)
+ if args.action == 'set_console_type':
+ set_console_type(args.console_type)
if args.action == 'rename':
rename_image(args.image_name, args.image_new_name)
if args.action == 'list':
list_images()
+ if args.action == 'list_console_types':
+ list_console_types()
exit()
diff --git a/src/op_mode/ipoe-control.py b/src/op_mode/ipoe-control.py
index 0f33beca7..b7d6a0c43 100755
--- a/src/op_mode/ipoe-control.py
+++ b/src/op_mode/ipoe-control.py
@@ -56,7 +56,11 @@ def main():
if args.selector in cmd_dict['selector'] and args.target:
run(cmd_dict['cmd_base'] + "{0} {1} {2}".format(args.action, args.selector, args.target))
else:
- output, err = popen(cmd_dict['cmd_base'] + cmd_dict['actions'][args.action], decode='utf-8')
+ if args.action == "show_sessions":
+ ses_pattern = " ifname,username,calling-sid,ip,ip6,ip6-dp,rate-limit,type,comp,state,uptime"
+ else:
+ ses_pattern = ""
+ output, err = popen(cmd_dict['cmd_base'] + cmd_dict['actions'][args.action] + ses_pattern, decode='utf-8')
if not err:
print(output)
else:
diff --git a/src/op_mode/nat.py b/src/op_mode/nat.py
index 2bc7e24fe..4ab524fb7 100755
--- a/src/op_mode/nat.py
+++ b/src/op_mode/nat.py
@@ -263,7 +263,7 @@ def _get_formatted_translation(dict_data, nat_direction, family, verbose):
proto = meta['layer4']['protoname']
if direction == 'independent':
conn_id = meta['id']
- timeout = meta['timeout']
+ timeout = meta.get('timeout', 'n/a')
orig_src = f'{orig_src}:{orig_sport}' if orig_sport else orig_src
orig_dst = f'{orig_dst}:{orig_dport}' if orig_dport else orig_dst
reply_src = f'{reply_src}:{reply_sport}' if reply_sport else reply_src
diff --git a/src/op_mode/ntp.py b/src/op_mode/ntp.py
new file mode 100644
index 000000000..e14cc46d0
--- /dev/null
+++ b/src/op_mode/ntp.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import csv
+import sys
+from itertools import chain
+
+import vyos.opmode
+from vyos.configquery import ConfigTreeQuery
+from vyos.utils.process import cmd
+
+def _get_raw_data(command: str) -> dict:
+ # Returns returns chronyc output as a dictionary
+
+ # Initialize dictionary keys to align with output of
+ # chrony -c. From some commands, its -c switch outputs
+ # more parameters, make sure to include them all below.
+ # See to chronyc(1) for definition of key variables
+ match command:
+ case "chronyc -c activity":
+ keys: list = [
+ 'sources_online',
+ 'sources_offline',
+ 'sources_doing_burst_return_online',
+ 'sources_doing_burst_return_offline',
+ 'sources_with_unknown_address'
+ ]
+
+ case "chronyc -c sources":
+ keys: list = [
+ 'm',
+ 's',
+ 'name_ip_address',
+ 'stratum',
+ 'poll',
+ 'reach',
+ 'last_rx',
+ 'last_sample_adj_offset',
+ 'last_sample_mes_offset',
+ 'last_sample_est_error'
+ ]
+
+ case "chronyc -c sourcestats":
+ keys: list = [
+ 'name_ip_address',
+ 'np',
+ 'nr',
+ 'span',
+ 'frequency',
+ 'freq_skew',
+ 'offset',
+ 'std_dev'
+ ]
+
+ case "chronyc -c tracking":
+ keys: list = [
+ 'ref_id',
+ 'ref_id_name',
+ 'stratum',
+ 'ref_time',
+ 'system_time',
+ 'last_offset',
+ 'rms_offset',
+ 'frequency',
+ 'residual_freq',
+ 'skew',
+ 'root_delay',
+ 'root_dispersion',
+ 'update_interval',
+ 'leap_status'
+ ]
+
+ case _:
+ raise ValueError(f"Raw mode: of {command} is not implemented")
+
+ # Get -c option command line output, splitlines,
+ # and save comma-separated values as a flat list
+ output = cmd(command).splitlines()
+ values = csv.reader(output)
+ values = list(chain.from_iterable(values))
+
+ # Divide values into chunks of size keys and transpose
+ if len(values) > len(keys):
+ values = _chunk_list(values,keys)
+ values = zip(*values)
+
+ return dict(zip(keys, values))
+
+def _chunk_list(in_list, n):
+ # Yields successive n-sized chunks from in_list
+ for i in range(0, len(in_list), len(n)):
+ yield in_list[i:i + len(n)]
+
+def _is_configured():
+ # Check if ntp is configured
+ config = ConfigTreeQuery()
+ if not config.exists("service ntp"):
+ raise vyos.opmode.UnconfiguredSubsystem("NTP service is not enabled.")
+
+def show_activity(raw: bool):
+ _is_configured()
+ command = f'chronyc'
+
+ if raw:
+ command += f" -c activity"
+ return _get_raw_data(command)
+ else:
+ command += f" activity"
+ return cmd(command)
+
+def show_sources(raw: bool):
+ _is_configured()
+ command = f'chronyc'
+
+ if raw:
+ command += f" -c sources"
+ return _get_raw_data(command)
+ else:
+ command += f" sources -v"
+ return cmd(command)
+
+def show_tracking(raw: bool):
+ _is_configured()
+ command = f'chronyc'
+
+ if raw:
+ command += f" -c tracking"
+ return _get_raw_data(command)
+ else:
+ command += f" tracking"
+ return cmd(command)
+
+def show_sourcestats(raw: bool):
+ _is_configured()
+ command = f'chronyc'
+
+ if raw:
+ command += f" -c sourcestats"
+ return _get_raw_data(command)
+ else:
+ command += f" sourcestats -v"
+ return cmd(command)
+
+if __name__ == '__main__':
+ try:
+ res = vyos.opmode.run(sys.modules[__name__])
+ if res:
+ print(res)
+ except (ValueError, vyos.opmode.Error) as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/op_mode/version.py b/src/op_mode/version.py
index ad0293aca..09d69ad1d 100755
--- a/src/op_mode/version.py
+++ b/src/op_mode/version.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2016-2022 VyOS maintainers and contributors
+# Copyright (C) 2016-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
@@ -30,11 +30,15 @@ from jinja2 import Template
version_output_tmpl = """
Version: VyOS {{version}}
Release train: {{release_train}}
+Release flavor: {{flavor}}
Built by: {{built_by}}
Built on: {{built_on}}
Build UUID: {{build_uuid}}
Build commit ID: {{build_git}}
+{%- if build_comment %}
+Build comment: {{build_comment}}
+{% endif %}
Architecture: {{system_arch}}
Boot via: {{boot_via}}
diff --git a/src/systemd/miniupnpd.service b/src/systemd/miniupnpd.service
deleted file mode 100644
index 51cb2eed8..000000000
--- a/src/systemd/miniupnpd.service
+++ /dev/null
@@ -1,13 +0,0 @@
-[Unit]
-Description=UPnP service
-ConditionPathExists=/run/upnp/miniupnp.conf
-After=vyos-router.service
-StartLimitIntervalSec=0
-
-[Service]
-WorkingDirectory=/run/upnp
-Type=simple
-ExecStart=/usr/sbin/miniupnpd -d -f /run/upnp/miniupnp.conf
-PrivateTmp=yes
-PIDFile=/run/miniupnpd.pid
-Restart=on-failure