diff --git a/.github/actions/vpp-checkout-gerrit-change/README.md b/.github/actions/vpp-checkout-gerrit-change/README.md deleted file mode 100644 index 550e755ad1c1..000000000000 --- a/.github/actions/vpp-checkout-gerrit-change/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# 🛠️ VPP Checkout Gerrit Change Action - -Fetches a Gerrit patchset from `origin` and checks out `FETCH_HEAD` in an existing VPP workspace. - -## Inputs - -| Input | Required | Default | Notes | -| --- | --- | --- | --- | -| `WORKSPACE` | Yes | none | Absolute path to the checked out VPP tree | -| `GERRIT_BRANCH` | Yes | none | Branch to checkout before fetching the patchset | -| `GERRIT_REFSPEC` | Yes | none | Gerrit refspec to fetch from `origin` | -| `LOG_DIR` | No | `/scratch/docker-build/vpp/logs/checkout` | Directory used for `checkout.log` | -| `TUI_LINE` | No | `*******************************************************************` | Log separator | - -## What It Runs - -```bash -git checkout "$GERRIT_BRANCH" || echo "WARNING: Could not checkout $GERRIT_BRANCH!" -git fetch --tags origin "$GERRIT_REFSPEC" -git checkout FETCH_HEAD -git show --stat -``` - -## Usage - -```yaml -- name: Checkout Gerrit change - uses: ./.github/actions/vpp-checkout-gerrit-change - with: - WORKSPACE: ${{ github.workspace }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_DIR: ${{ github.workspace }}/logs/checkout -``` diff --git a/.github/actions/vpp-checkout-gerrit-change/action.yml b/.github/actions/vpp-checkout-gerrit-change/action.yml deleted file mode 100644 index b10c2d57d46b..000000000000 --- a/.github/actions/vpp-checkout-gerrit-change/action.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- -name: "🛠️ VPP Checkout Gerrit Change" -description: | - This GitHub Action checks out the VPP Gerrit change specified by the input parameters. - -inputs: - LOG_DIR: - description: "Directory for checkout logs" - required: false - default: "/scratch/docker-build/vpp/logs/checkout" - type: string - WORKSPACE: - description: "Workspace directory where VPP is located" - required: true - type: string - GERRIT_BRANCH: - description: "Gerrit branch to checkout" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec to fetch" - required: true - type: string - TUI_LINE: - description: "Delimiter line for TUI output" - required: false - default: "*******************************************************************" - type: string - -runs: - using: "composite" - steps: - - name: Get Gerrit Change - shell: bash - env: - LOG_DIR: ${{ inputs.LOG_DIR }} - WORKSPACE: ${{ inputs.WORKSPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - TUI_LINE: ${{ inputs.TUI_LINE }} - run: | - set -euxo pipefail - mkdir -p "$LOG_DIR" - { - echo "$TUI_LINE" - echo "* Checkout $GERRIT_BRANCH ($GERRIT_REFSPEC)" - echo "$TUI_LINE" - cd "$WORKSPACE" - git checkout "$GERRIT_BRANCH" || echo "WARNING: Could not checkout $GERRIT_BRANCH!" - git fetch --all origin "$GERRIT_REFSPEC" - git checkout FETCH_HEAD - git show --stat - } | tee "$LOG_DIR/checkout.log" diff --git a/.github/workflows/build-deb-trixie.yml b/.github/workflows/build-deb-trixie.yml new file mode 100644 index 000000000000..a1823187e224 --- /dev/null +++ b/.github/workflows/build-deb-trixie.yml @@ -0,0 +1,189 @@ +name: Build VPP DEB for Debian 13 (Trixie) + +on: + push: + branches: [master] + workflow_dispatch: + inputs: + release: + description: "Publish as GitHub Release" + type: boolean + default: false + tag: + description: "Release tag (if publishing)" + type: string + default: "" + +jobs: + build: + runs-on: ubuntu-24.04 + permissions: + contents: write + container: + image: debian:trixie + env: + CCACHE_COMPRESS: "true" + CCACHE_MAXSIZE: 2G + steps: + - name: Disable APT cache cleanup + run: rm -f /etc/apt/apt.conf.d/docker-clean + + - name: Install base tools + run: | + apt-get update -qq + apt-get install -y -qq sudo make git + + - name: Clone repository + run: | + git clone --depth 1 --branch "$GITHUB_REF_NAME" \ + "https://github.com/$GITHUB_REPOSITORY.git" . + # The branch can move between the event and this clone, so check out + # the commit the run is for instead of whatever the branch points at + # now; otherwise the debs and their version string would name a + # different commit than the run and the release body do. + git config --global --add safe.directory "$PWD" + if [ "$(git rev-parse HEAD)" != "$GITHUB_SHA" ]; then + git fetch --no-tags --depth 1 origin "$GITHUB_SHA" + git checkout --detach FETCH_HEAD + fi + + # "make install-dep" below is the only step that reads this cache, and + # the package list it installs, $(DEB_DEPENDS), is defined in the + # Makefile. Key the cache by that file, so the key moves when the + # package list moves: the run that first needs a new package is then the + # run that stores it. Hash it into an output rather than with + # hashFiles, because the install step rewrites the Makefile between the + # restore and the save and both steps have to ask for the same key. + # Reading the file at all needs the clone, and the base tools install + # above runs before this point, so it reads no cache. + - name: Hash the dependency list + id: deps + shell: bash + run: echo "hash=$(git hash-object Makefile)" >> $GITHUB_OUTPUT + + - name: Restore apt archive cache + uses: actions/cache/restore@v4 + with: + path: /var/cache/apt/archives + key: apt-debian13-${{ runner.arch }}-${{ steps.deps.outputs.hash }} + restore-keys: | + apt-debian13-${{ runner.arch }}- + + - name: Compute short SHA and build date + id: vars + shell: bash + run: | + git config --global --add safe.directory "$PWD" + echo "short_sha=$(git rev-parse --short=7 HEAD)" >> $GITHUB_OUTPUT + # The release body used github.event.head_commit.timestamp, which a + # workflow_dispatch run does not carry; the github context has no run + # start time either, so record one here. + echo "date=$(date -u '+%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + + - name: Install VPP build dependencies + run: | + sed -i '/^\s*git config commit\.template/s/^\(\s*\)/\1-/' Makefile + make UNATTENDED=yes install-dep + + - name: Save apt archive cache + if: always() + uses: actions/cache/save@v4 + with: + path: /var/cache/apt/archives + key: apt-debian13-${{ runner.arch }}-${{ steps.deps.outputs.hash }} + + - name: Restore ccache + uses: actions/cache/restore@v4 + with: + path: build-root/.ccache + # Keyed by commit, not by ref alone. With a fixed key only the + # first build of a ref could save this entry; every later build + # asked to save a key that was already stored, so the ccache stayed + # at whatever the first build left in it. A commit key gives each + # build a fresh entry to save, and the prefix in restore-keys still + # lends a run the newest entry of another commit on the same ref. + key: ccache-debian13-${{ runner.arch }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ccache-debian13-${{ runner.arch }}-${{ github.ref_name }}- + ccache-debian13-${{ runner.arch }}- + + - name: Build VPP DEB packages + run: | + git config --global --add safe.directory "$PWD" + git fetch --tags --force 2>/dev/null || true + # The version below counts the commits in the history, so it needs + # the whole history; the clone above is shallow. Asking for the + # unshallow only when the history is short keeps a full repository + # from erroring out, and the warning keeps a failure here from + # quietly turning the count into a constant 1. + if [ "$(git rev-parse --is-shallow-repository)" = true ]; then + git fetch --unshallow --quiet || + echo "::warning::full history unavailable, the commit count in the version is not reliable" + fi + # Only a VPP release tag (vYY.MM...) should win over the synthesised + # version. A bare 'v*' also matches this workflow's own deb release + # tags (vpp-trixie-), which would leave .version unwritten and + # send src/scripts/version to its own git describe; that one does not + # look at lightweight tags, so it would find none and exit 1, leaving + # the package version empty. + if ! git describe --tags --long --match 'v[0-9]*' 2>/dev/null; then + # No matching tag in this repo, so synthesise one. The build + # identifier must vary per commit, otherwise every build of this + # branch produces the same version string and apt skips packages + # it considers already installed. + # + # The "0.0.0-release" prefix is deliberate: it is the version of + # packages already installed from this branch, and Debian + # compares "-" by its last hyphen, so a plain + # "0.0.0-~g" would sort BELOW "0.0.0-release" (letters + # outrank digits) and apt would refuse it. Extending the existing + # "-release" revision keeps every new build strictly newer. + # + # The commit timestamp alone does not order two commits made in + # the same second: Debian then falls through to the hash, which + # carries no order, so the child can sort below its parent and apt + # keeps the older deb. Append the commit count, which grows + # strictly along the parent chain. Counting commits is why the + # fetch above has to unshallow the clone; at --depth 1 the count + # is 1 for every commit. + # + # Keep the separator in front of the hash: src/scripts/version + # turns it into "~g" in the package version. Glued to the + # timestamp the hash joins the same digit run, and Debian compares + # digit runs by length before value, so a build whose hash starts + # with a digit outranks every later build with a shorter run. + echo "0.0.0-release+$(git log -1 --format=%ct).$(git rev-list --count HEAD)-g$(git rev-parse --short=7 HEAD)" \ + > src/scripts/.version + fi + ccache -z + export VPP_EXTRA_CMAKE_ARGS="\ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" + make pkg-deb + ccache -s + + - name: Save ccache + uses: actions/cache/save@v4 + with: + path: build-root/.ccache + key: ccache-debian13-${{ runner.arch }}-${{ github.ref_name }}-${{ github.sha }} + + - name: Upload DEB artifacts + uses: actions/upload-artifact@v4 + with: + name: vpp-debian13-trixie + path: build-root/*.deb + + - name: Publish Release + if: ${{ github.event_name == 'workflow_dispatch' && inputs.release }} + uses: softprops/action-gh-release@v2 + with: + files: build-root/*.deb + tag_name: ${{ inputs.tag || format('vpp-trixie-{0}', github.run_number) }} + name: "VPP ${{ steps.vars.outputs.short_sha }} for Debian 13" + body: | + VPP build from commit ${{ github.sha }} + - Branch: ${{ github.ref_name }} + - Build date: ${{ steps.vars.outputs.date }} + draft: false + prerelease: true diff --git a/.github/workflows/gerrit-comment-handler.yml b/.github/workflows/gerrit-comment-handler.yml deleted file mode 100644 index 3ca37acb9526..000000000000 --- a/.github/workflows/gerrit-comment-handler.yml +++ /dev/null @@ -1,219 +0,0 @@ -name: gerrit-comment-handler - -# yamllint disable-line rule:truthy -on: - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - GERRIT_COMMENT: - description: "Full command line from Gerrit comment" - required: true - type: string - -concurrency: - group: ${{ github.workflow }}-${{ inputs.GERRIT_PROJECT }}-${{ inputs.GERRIT_BRANCH }}-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - notify-start: - runs-on: ubuntu-latest - outputs: - log_prefix: ${{ steps.start-time.outputs.log_prefix }} - - steps: - - name: Start Time - id: start-time - run: | - set -euxo pipefail - timestamp=$(date -u +%Y_%m_%d_%H%M%S_UTC) - log_prefix="gha-vpp-gerrit-patchset/csit-perf-${{ inputs.GERRIT_BRANCH }}-${timestamp}-gerrit-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.GERRIT_PATCHSET_NUMBER}}" - echo "log_prefix=$log_prefix" >> "$GITHUB_OUTPUT" - echo "Gerrit Change: ${{ inputs.GERRIT_CHANGE_URL }}" - - - name: Job Details - run: | - { - echo "### Gerrit Comment Handler" - echo - echo "| Field | Value |" - echo "| --- | --- |" - echo "| Gerrit change | ${{ inputs.GERRIT_CHANGE_URL }}/${{ inputs.GERRIT_PATCHSET_NUMBER }} |" - echo "| Branch | ${{ inputs.GERRIT_BRANCH }} |" - echo "| Gerrit Change ID | ${{ inputs.GERRIT_CHANGE_ID }} |" - echo "| Patchset Revision | ${{ inputs.GERRIT_PATCHSET_REVISION }} |" - echo "| Refspec | ${{ inputs.GERRIT_REFSPEC }} |" - echo "| Gerrit Event | ${{ inputs.GERRIT_EVENT_TYPE }} |" - echo "| Gerrit Comment | ${{ inputs.GERRIT_COMMENT }} |" - echo "| Namespace | ${{ inputs.FDIO_NAMESPACE }} |" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Clear Gerrit vote - # yamllint disable-line rule:line-length - uses: lfreleng-actions/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_REQUIRED_USER }} - key: ${{ secrets.GERRIT_SSH_REQUIRED_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: clear - comment-only: true - - dispatch: - runs-on: ubuntu-latest - - outputs: - matrix: ${{ steps.set-matrix.outputs.matrix }} - params: ${{ steps.set-matrix.outputs.params }} - - steps: - - name: Prepare workflow selection matrix - id: set-matrix - uses: fdio/csit/.github/actions/csit-comment-dispatch@1f3de8c4266cc118bbf3e108dd0f33a42b0cc70f - with: - gerrit_comment: ${{ github.event.inputs.GERRIT_COMMENT }} - - vpp-csit-verify-handler: - needs: dispatch - # yamllint disable-line rule:line-length - name: vpp-csit-perf-verify-${{ matrix.node }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=builder - - fdio:size=csit - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - timeout-minutes: 1440 - - strategy: - matrix: ${{ fromJSON(needs.dispatch.outputs.matrix) }} - - env: - # yamllint disable-line rule:line-length - BUILD_TAG: vpp-csit-verify-perf-${{ github.event.inputs.GERRIT_BRANCH }}-${{ matrix.os }}-${{ matrix.node }}-${{ github.run_id }} - # yamllint disable-line rule:line-length - JOB_NAME: vpp-csit-verify-perf-${{ github.event.inputs.GERRIT_BRANCH }}-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ matrix.node }} - WORKSPACE: /scratch/docker-build/vpp - CSIT_WORKSPACE: /scratch/docker-build/csit - GERRIT_BRANCH: ${{ github.event.inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ github.event.inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ github.event.inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ github.event.inputs.GERRIT_REFSPEC }} - GERRIT_CHANGE_NUMBER: ${{ github.event.inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ github.event.inputs.GERRIT_PATCHSET_NUMBER }} - LOG_PREFIX: ${{ github.event.inputs.LOG_PREFIX }} - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - TUI_LINE: "*******************************************************************" - GERRIT_EVENT_TYPE: comment-added - GERRIT_EVENT_COMMENT_TEXT: ${{ needs.dispatch.outputs.params }} - - steps: - - name: Gerrit Checkout VPP - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Gerrit Checkout CSIT - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.CSIT_WORKSPACE }} - GERRIT_BRANCH: master - GERRIT_REFSPEC: "" - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Run CSIT Bootstrap - uses: fdio/csit/.github/actions/csit-bootstrap@6e2e8dd6eae1a9297bfcc3a1630be181191a0553 - with: - bootstrap_script: per_patch_perf.sh - path: ${{ env.CSIT_WORKSPACE }} - with_oper: true - - - name: AWS S3 Publish Logs - if: ${{ !cancelled() && (success() || failure()) }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - # yamllint disable-line rule:line-length - s3_path: vpp-csit-verify-perf-${{ matrix.node }}/${{ github.run_id }} - archives_path: ${{ env.CSIT_WORKSPACE }}/archives - - vote: - if: ${{ always() }} - needs: - - notify-start - - vpp-csit-verify-handler - runs-on: ubuntu-latest - steps: - - uses: technote-space/workflow-conclusion-action@45ce8e0eb155657ab8ccf346ade734257fd196a5 # v3.0.3 archived Nov 16, 2023 - - name: Set vote - uses: lfit/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_REQUIRED_USER }} - key: ${{ secrets.GERRIT_SSH_REQUIRED_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ github.event.inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ github.event.inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: ${{ env.WORKFLOW_CONCLUSION }} - comment-only: true diff --git a/.github/workflows/gerrit-merge.yml b/.github/workflows/gerrit-merge.yml deleted file mode 100644 index db967e4e8fe6..000000000000 --- a/.github/workflows/gerrit-merge.yml +++ /dev/null @@ -1,253 +0,0 @@ -name: gerrit-merge - -on: - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.GERRIT_PROJECT }}-${{ inputs.GERRIT_BRANCH }}-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - start-merge: - name: Start Merge - runs-on: ubuntu-latest - outputs: - log_prefix: ${{ steps.start-time.outputs.log_prefix }} - steps: - - name: Start Time - id: start-time - run: | - set -euxo pipefail - timestamp=$(date -u +%Y_%m_%d_%H%M%S_UTC) - branch=${{ inputs.GERRIT_BRANCH }} - branch=${branch/\//-} - log_prefix="gha-vpp-gerrit-patchset/merge-${branch}-${timestamp}-gerrit-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.GERRIT_PATCHSET_NUMBER}}" - echo "log_prefix=$log_prefix" >> "$GITHUB_OUTPUT" - echo "Gerrit Change: ${{ inputs.GERRIT_CHANGE_URL }}" - echo "Gerrit Event Type: ${{ inputs.GERRIT_EVENT_TYPE }}" - - - name: Job Details - run: | - { - echo "### Gerrit Merge Handler" - echo - echo "| Field | Value |" - echo "| --- | --- |" - echo "| Gerrit change | ${{ inputs.GERRIT_CHANGE_URL }} |" - echo "| Branch | ${{ inputs.GERRIT_BRANCH }} |" - echo "| Gerrit Change ID | ${{ inputs.GERRIT_CHANGE_ID }} |" - echo "| Patchset Revision | ${{ inputs.GERRIT_PATCHSET_REVISION }} |" - echo "| Refspec | ${{ inputs.GERRIT_REFSPEC }} |" - echo "| Gerrit Event | ${{ inputs.GERRIT_EVENT_TYPE }} |" - echo "| Gerrit Comment | ${{ inputs.GERRIT_COMMENT }} |" - echo "| Namespace | ${{ inputs.FDIO_NAMESPACE }} |" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Clear Gerrit Vote - # yamllint disable-line rule:line-length - uses: lfreleng-actions/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_REQUIRED_USER }} - key: ${{ secrets.GERRIT_SSH_REQUIRED_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: clear - comment-only: true # Just set a comment, do not vote on gerrit - - merge-maketest: - name: Merge Maketest - needs: - - start-merge - uses: fdio/vpp/.github/workflows/vpp-merge-maketest.yml@cbbb3d4cae9524b7b93a0fff7592555ff72462ea - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: refs/heads/${{ inputs.GERRIT_BRANCH }} - LOG_PREFIX: ${{ needs.start-merge.outputs.log_prefix }} - secrets: inherit - - detect-docs-changes: - name: Detect Docs Changes - needs: - - start-merge - runs-on: - - self-hosted - - nomad - - fdio:arch=x86_64 - - fdio:class=builder - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=ubuntu2404 - - fdio:size=csit - - fdio:workflow=${{ github.run_id }} - outputs: - docs_changed: ${{ steps.docs-changed.outputs.docs_changed }} - env: - WORKSPACE: /scratch/docker-build/vpp - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - TUI_LINE: "*******************************************************************" - steps: - - name: Setup Environment - id: setup-executor-env - continue-on-error: true - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - id: checkout-vpp - continue-on-error: true - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@09552c71554236d36c1eb51a1740a366019bc1a5 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Detect docs changes - id: docs-changed - if: ${{ always() }} - env: - CHECKOUT_OUTCOME: ${{ steps.checkout-vpp.outcome }} - run: | - set -euxo pipefail - - docs_changed="true" - if [[ "$CHECKOUT_OUTCOME" == "success" ]]; then - cd "$WORKSPACE" - if git diff-tree --no-commit-id --name-only -r --root HEAD | grep -Eq '(^docs/|\.rst$)'; then - docs_changed="true" - else - docs_changed="false" - fi - else - echo "warn: unable to checkout '${GERRIT_REFSPEC}'; defaulting docs_changed=true" >&2 - fi - - echo "docs_changed=$docs_changed" >> "$GITHUB_OUTPUT" - - merge-docs: - name: Merge Docs - needs: - - start-merge - - detect-docs-changes - if: ${{ needs.detect-docs-changes.outputs.docs_changed == 'true' }} - uses: fdio/vpp/.github/workflows/vpp-merge-docs.yml@e5c8759e531c155d1aa983fcb0fe84fd1f004c88 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: refs/heads/${{ inputs.GERRIT_BRANCH }} - LOG_PREFIX: ${{ needs.start-merge.outputs.log_prefix }} - secrets: inherit - - final-vote: - name: Vote - if: ${{ always() }} - needs: - - start-merge - - detect-docs-changes - - merge-maketest - - merge-docs - runs-on: ubuntu-latest - env: - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - steps: - - name: Collect workflow results - id: overall - run: | - set -euxo pipefail - docs_ok="true" - if [[ "${{ needs.detect-docs-changes.outputs.docs_changed }}" == "true" ]]; then - [[ "${{ needs.merge-docs.result }}" == "success" ]] || docs_ok="false" - fi - - if [[ "${{ needs.start-merge.result }}" == "success" && \ - "${{ needs.merge-maketest.result }}" == "success" && \ - "$docs_ok" == "true" ]] ; then - echo "result=success" >> "$GITHUB_OUTPUT" - else - echo "result=failure" >> "$GITHUB_OUTPUT" - fi - - - name: Gerrit Vote Disabled - # yamllint disable-line rule:line-length - uses: lfreleng-actions/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_USER }} - key: ${{ secrets.GERRIT_SSH_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ env.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ env.GERRIT_PATCHSET_NUMBER }} - vote-type: ${{ steps.overall.outputs.result }} - comment-only: true # Just set a comment, do not vote on gerrit diff --git a/.github/workflows/gerrit-verify.yml b/.github/workflows/gerrit-verify.yml deleted file mode 100644 index 2b41ffa8d281..000000000000 --- a/.github/workflows/gerrit-verify.yml +++ /dev/null @@ -1,355 +0,0 @@ -name: gerrit-verify - -on: - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.GERRIT_PROJECT }}-${{ inputs.GERRIT_BRANCH }}-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - start-verify: - name: Start Verify - runs-on: ubuntu-latest - outputs: - log_prefix: ${{ steps.start-time.outputs.log_prefix }} - - steps: - - name: Start Time - id: start-time - run: | - set -euxo pipefail - timestamp=$(date -u +%Y_%m_%d_%H%M%S_UTC) - branch=${{ inputs.GERRIT_BRANCH }} - branch=${branch/\//-} - log_prefix="gha-vpp-gerrit-patchset/verify-${branch}-${timestamp}-gerrit-${{ inputs.GERRIT_CHANGE_NUMBER }}-${{ inputs.GERRIT_PATCHSET_NUMBER}}" - echo "log_prefix=$log_prefix" >> "$GITHUB_OUTPUT" - echo "Gerrit Change: ${{ inputs.GERRIT_CHANGE_URL }}" - - - name: Job Details - run: | - { - echo "### Gerrit Verify Handler" - echo - echo "| Field | Value |" - echo "| --- | --- |" - echo "| Gerrit change | ${{ inputs.GERRIT_CHANGE_URL }}/${{ inputs.GERRIT_PATCHSET_NUMBER }} |" - echo "| Branch | ${{ inputs.GERRIT_BRANCH }} |" - echo "| Gerrit Change ID | ${{ inputs.GERRIT_CHANGE_ID }} |" - echo "| Patchset Revision | ${{ inputs.GERRIT_PATCHSET_REVISION }} |" - echo "| Refspec | ${{ inputs.GERRIT_REFSPEC }} |" - echo "| Gerrit Event | ${{ inputs.GERRIT_EVENT_TYPE }} |" - echo "| Gerrit Comment | ${{ inputs.GERRIT_COMMENT }} |" - echo "| Namespace | ${{ inputs.FDIO_NAMESPACE }} |" - } >> "$GITHUB_STEP_SUMMARY" - - - name: Clear Gerrit vote - # yamllint disable-line rule:line-length - uses: lfreleng-actions/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_REQUIRED_USER }} - key: ${{ secrets.GERRIT_SSH_REQUIRED_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ inputs.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - vote-type: clear - comment-only: true - - verify-checkstyle: - name: Checkstyle - needs: - - start-verify - uses: fdio/vpp/.github/workflows/vpp-verify-checkstyle.yml@aea0a31a2b6bba3b9e47feccae473f4206f85704 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - verify-maketest: - name: Maketest - needs: - - start-verify - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' }} - uses: fdio/vpp/.github/workflows/vpp-verify-maketest.yml@cbbb3d4cae9524b7b93a0fff7592555ff72462ea - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - verify-gcc: - name: GCC - needs: - - start-verify - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' }} - uses: fdio/vpp/.github/workflows/vpp-verify-gcc.yml@e5c8759e531c155d1aa983fcb0fe84fd1f004c88 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - verify-csit-api: - name: CSIT API - needs: - - start-verify - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' }} - uses: fdio/vpp/.github/workflows/vpp-csit-verify-api.yml@e5c8759e531c155d1aa983fcb0fe84fd1f004c88 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - verify-arm-drivers: - name: ARM Drivers - needs: - - start-verify - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' }} - uses: fdio/vpp/.github/workflows/vpp-verify-arm-drivers.yml@e5c8759e531c155d1aa983fcb0fe84fd1f004c88 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - verify-hst: - name: HST - needs: - - start-verify - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' }} - uses: fdio/vpp/.github/workflows/vpp-verify-hst.yml@f2443416568f44a73be9d94758f3077a43e8b1f0 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - detect-docs-changes: - name: Detect Docs Changes - needs: - - start-verify - - verify-checkstyle - runs-on: - - self-hosted - - nomad - - fdio:arch=x86_64 - - fdio:class=builder - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=ubuntu2404 - - fdio:size=csit - - fdio:workflow=${{ github.run_id }} - outputs: - docs_changed: ${{ steps.docs-changed.outputs.docs_changed }} - env: - WORKSPACE: /scratch/docker-build/vpp - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - TUI_LINE: "*******************************************************************" - steps: - - name: Setup Environment - id: setup-executor-env - continue-on-error: true - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - id: checkout-vpp - continue-on-error: true - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Detect docs changes - id: docs-changed - if: ${{ always() }} - env: - CHECKOUT_OUTCOME: ${{ steps.checkout-vpp.outcome }} - run: | - set -euxo pipefail - - docs_changed="true" - if [[ "$CHECKOUT_OUTCOME" == "success" ]]; then - cd "$WORKSPACE" - if git diff-tree --no-commit-id --name-only -r --root HEAD | grep -Eq '(^docs/|\.rst$)'; then - docs_changed="true" - else - docs_changed="false" - fi - else - echo "warn: unable to checkout '${GERRIT_REFSPEC}'; defaulting docs_changed=true" >&2 - fi - - echo "docs_changed=$docs_changed" >> "$GITHUB_OUTPUT" - - verify-docs: - name: Docs - needs: - - start-verify - - detect-docs-changes - - verify-checkstyle - if: ${{ needs.verify-checkstyle.result == 'success' && needs.detect-docs-changes.outputs.docs_changed == 'true' }} - uses: fdio/vpp/.github/workflows/vpp-verify-docs.yml@09552c71554236d36c1eb51a1740a366019bc1a5 - with: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ needs.start-verify.outputs.log_prefix }} - secrets: inherit - - final-vote: - name: Vote - if: ${{ always() }} - needs: - - start-verify - - detect-docs-changes - - verify-checkstyle - - verify-maketest - - verify-gcc - - verify-docs - - verify-csit-api - - verify-arm-drivers - - verify-hst - runs-on: ubuntu-latest - env: - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - steps: - - name: Collect workflow results - id: overall - run: | - set -euxo pipefail - docs_ok="true" - if [[ "${{ needs.detect-docs-changes.outputs.docs_changed }}" == "true" ]]; then - [[ "${{ needs.verify-docs.result }}" == "success" ]] || docs_ok="false" - fi - - if [[ "${{ needs.start-verify.result }}" == "success" && \ - "${{ needs.verify-checkstyle.result }}" == "success" && \ - "${{ needs.verify-maketest.result }}" == "success" && \ - "${{ needs.verify-gcc.result }}" == "success" && \ - "$docs_ok" == "true" && \ - "${{ needs.verify-csit-api.result }}" == "success" && \ - "${{ needs.verify-arm-drivers.result }}" == "success" && \ - "${{ needs.verify-hst.result }}" == "success" ]]; then - echo "result=success" >> "$GITHUB_OUTPUT" - else - echo "result=failure" >> "$GITHUB_OUTPUT" - fi - - - name: Gerrit Vote - # yamllint disable-line rule:line-length - uses: lfreleng-actions/gerrit-review-action@a5de1d5bf17c2603ae81544dc2a455017b454ec8 # 1.1.1 - with: - host: ${{ vars.GERRIT_SERVER }} - username: ${{ vars.GERRIT_SSH_USER }} - key: ${{ secrets.GERRIT_SSH_PRIVKEY }} - known_hosts: ${{ vars.GERRIT_KNOWN_HOSTS }} - gerrit-change-number: ${{ env.GERRIT_CHANGE_NUMBER }} - gerrit-patchset-number: ${{ env.GERRIT_PATCHSET_NUMBER }} - vote-type: ${{ steps.overall.outputs.result }} - comment-only: false # Vote on gerrit diff --git a/.github/workflows/github2gerrit.yaml b/.github/workflows/github2gerrit.yaml deleted file mode 100644 index 90fa062c7e53..000000000000 --- a/.github/workflows/github2gerrit.yaml +++ /dev/null @@ -1,68 +0,0 @@ ---- -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: 2025 The Linux Foundation - -name: 'GitHub2Gerrit' - -# yamllint disable-line rule:truthy -on: - workflow_dispatch: - inputs: - preserve_github_prs: - description: "Do NOT close GitHub PRs after pushing to Gerrit" - required: false - default: false - type: boolean - allow_duplicates: - description: "Allow duplicate changes to be raised in Gerrit" - required: false - default: false - type: boolean - - pull_request_target: - types: [opened, reopened, edited, synchronize] - branches: - - master - - main - -concurrency: - group: ${{ github.workflow }}-${{ github.run_id }} - cancel-in-progress: true - -jobs: - github2gerrit: - name: 'GitHub2Gerrit' - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: write - timeout-minutes: 12 - steps: - # Harden the runner used by this workflow - # yamllint disable-line rule:line-length - - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 - name: 'Harden runner' - with: - egress-policy: audit - - - name: 'Checkout repository' - # yamllint disable-line rule:line-length - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - with: - fetch-depth: 10 - ref: ${{ github.event.pull_request.head.sha || github.sha }} - - - name: 'Run GitHub2Gerrit Action' - # yamllint disable-line rule:line-length - uses: lfreleng-actions/github2gerrit-action@7c1ba15a75dd86619139a81933f4432648d97af5 # v1.0.5 - with: - USE_PR_AS_COMMIT: true - ALLOW_DUPLICATES: ${{ inputs.allow_duplicates }} - PRESERVE_GITHUB_PRS: ${{ inputs.preserve_github_prs }} - ISSUE_ID_LOOKUP_JSON: ${{ vars.ISSUE_ID_LOOKUP_JSON }} - GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }} - GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }} - GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }} - GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }} - VERBOSE: true diff --git a/.github/workflows/periodic-vpp-coverity.yml b/.github/workflows/periodic-vpp-coverity.yml deleted file mode 100644 index 51ff2a5e3a14..000000000000 --- a/.github/workflows/periodic-vpp-coverity.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: periodic-vpp-coverity - -on: - schedule: - - cron: '0 3 * * *' # Runs daily at 3am UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - coverity-build: - # yamllint disable-line rule:line-length - name: coverity-build-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['debug'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Timestamp and Log Directory - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="coverity-build-master-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Run Coverity Build Script - run: | - set -euo pipefail - - cd $WORKSPACE - COVERITY_TOKEN=${{ secrets.FDIO_VPP_COVERITY_TOKEN }} $WORKSPACE/extras/scripts/coverity-build | tee $LOG_DIR/coverity-build.log - - - name: AWS S3 Publish Logs - if: ${{ always() }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: gha-vpp-periodic/${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/periodic-vpp-verify-asan-hst.yml b/.github/workflows/periodic-vpp-verify-asan-hst.yml deleted file mode 100644 index 2f57912b82e0..000000000000 --- a/.github/workflows/periodic-vpp-verify-asan-hst.yml +++ /dev/null @@ -1,181 +0,0 @@ -name: periodic-vpp-verify-asan-hst - -on: - schedule: - - cron: '0 4 * * *' # Runs daily at 4am UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - verify-asan-hst-periodic: - # yamllint disable-line rule:line-length - name: asan-hst-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ matrix.build_type }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:size=${{ matrix.size }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - include: - # Debug builds on x86_64 - - build_type: 'debug' - os: 'ubuntu2404' - executor_arch: 'x86_64' - class: 'hst' - size: 'hst' - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - HS_TEST_DIR: /scratch/docker-build/vpp/test/hs-test - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: "auto" - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Set Timestamp and Log Path - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="gha-vpp-periodic/vpp-verify-asan-hst-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: ASAN HST VPP Build - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@72cac6c0bc22441f24ca057a1be8919a083c578f - timeout-minutes: 90 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - BUILD_HST: 'true' - ENABLE_ASAN: 'true' - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: ASAN HST Test VPP - if: success() - uses: fdio/vpp/.github/actions/vpp-hst-test@5c0def01c0f8a17f90a1b16c9039077e298e0adc - timeout-minutes: 180 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix['build_type'] }} - HS_TEST_DIR: ${{ env.HS_TEST_DIR }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/periodic-vpp-verify-asan-maketest.yml b/.github/workflows/periodic-vpp-verify-asan-maketest.yml deleted file mode 100644 index ed23ee85e730..000000000000 --- a/.github/workflows/periodic-vpp-verify-asan-maketest.yml +++ /dev/null @@ -1,178 +0,0 @@ -name: periodic-vpp-verify-asan-maketest - -on: - schedule: - - cron: '0 4 * * *' # Runs daily at 4am UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - verify-asan-maketest-periodic: - # yamllint disable-line rule:line-length - name: asan-maketest-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ matrix.build_type }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - include: - # Debug builds on x86_64 - - build_type: 'debug' - os: 'ubuntu2404' - executor_arch: 'x86_64' - class: 'builder' - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - MAKE_TEST_OS: 'ubuntu-24.04' - TEST_JOBS: 4 - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Set Timestamp and Log Path - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="gha-vpp-periodic/vpp-verify-asan-maketest-master-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: ASAN MAKETEST VPP Build - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@72cac6c0bc22441f24ca057a1be8919a083c578f - timeout-minutes: 90 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - ENABLE_ASAN: 'true' - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: ASAN MAKETEST VPP - if: success() - uses: fdio/vpp/.github/actions/vpp-make-test@03f35503fee57df2348617498db28fde557a3880 - timeout-minutes: 180 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix.build_type }} - TEST_JOBS: ${{ env.TEST_JOBS }} - MAKE_TEST_OS: ${{ env.MAKE_TEST_OS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/periodic-vpp-verify-cov.yml b/.github/workflows/periodic-vpp-verify-cov.yml deleted file mode 100644 index fcab75cafaf9..000000000000 --- a/.github/workflows/periodic-vpp-verify-cov.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: periodic-vpp-verify-cov - -on: - schedule: - - cron: '0 0 * * *' # Runs daily at midnight UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - verify-cov: - # yamllint disable-line rule:line-length - name: cov-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - S3_DOCS_7DAY_PATH: "" - S3_DOCS_7DAY_URL: https://s3-docs-7day.fd.io/vex-yul-rot-jenkins-1 - WORKSPACE: /scratch/docker-build/vpp - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: 4 - HS_PARALLEL: auto - TEST_RETRIES: 3 - MAKE_TEST_OS: ubuntu-24.04 - VPPAPIGEN_TEST_OS: ubuntu-24.04 - SHM_SIZE: 2048M - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Set Timestamp and Log Path - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="gha-vpp-periodic/vpp-coverage-master-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - echo "S3_DOCS_7DAY_PATH=$LOG_NAME/docs" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Setup Docker VPP Runtime Environment for 'make test' - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-docker-runtime-setup@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - SHM_SIZE: ${{ env.SHM_SIZE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: VPP Make Coverage Test - run: | - cd "$WORKSPACE" - set -x - { - make COMPRESS_FAILED_TEST_LOGS=yes TEST_JOBS=$TEST_JOBS \ - HS_PARALLEL=$HS_PARALLEL CCACHE_DISABLE=1 test-cov-both || true - } | tee "$LOG_DIR/make_coverage_test.log" - - - name: AWS S3 Publish Logs - if: ${{ always() }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: AWS S3 Publish Coverage Report - if: ${{ always() }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_DOCS_7DAY_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_DOCS_7DAY_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_DOCS_7DAY_BUCKET}} - s3_path: ${{ env.S3_DOCS_7DAY_PATH }} - s3_url: ${{ env.S3_DOCS_7DAY_URL }} - archives_path: "${{ env.WORKSPACE }}/build-root/test-coverage-merged/html" - compress_files: false - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/periodic-vpp-verify-dpdk-rdma-ver.yml b/.github/workflows/periodic-vpp-verify-dpdk-rdma-ver.yml deleted file mode 100644 index b54ffb653127..000000000000 --- a/.github/workflows/periodic-vpp-verify-dpdk-rdma-ver.yml +++ /dev/null @@ -1,138 +0,0 @@ -name: periodic-vpp-verify-dpdk-rdma-ver - -on: - schedule: - - cron: '0 2 * * *' # Runs daily at 2am UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - verify-dpdk-rdma-version: - # yamllint disable-line rule:line-length - name: dpdk-rdma-ver-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Timestamp and Log Directory - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="dpdk-rdma-version-master-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Build VPP and Verify DPDK RDMA driver - run: | - set -euo pipefail - - EXTERNAL_BUILD_DIR="$WORKSPACE/build/external" - RETVAL="0" - RESULT="pass" - MISMATCH_RESULT="INCLUDED IN" - - make -C "$EXTERNAL_BUILD_DIR" build-deb | tee "$LOG_DIR/build_deb.log" - source "$EXTERNAL_BUILD_DIR/dpdk_mlx_default.sh" || true - - if [ "${DPDK_MLX_DEFAULT-}" = "n" ] ; then - MISMATCH_RESULT="MISSING FROM" - RETVAL="1" - RESULT="fail" - fi - echo -e "\n$TUI_LINE\n* MLX DPDK DRIVER $MISMATCH_RESULT VPP-EXT-DEPS PACKAGE\n$TUI_LINE\n" | tee "$LOG_DIR/result_${RESULT}.log" - exit $RETVAL - - - name: AWS S3 Publish Logs - if: ${{ always() }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: gha-vpp-periodic/${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/periodic-vpp-verify-hst.yml b/.github/workflows/periodic-vpp-verify-hst.yml deleted file mode 100644 index aa45d6b46412..000000000000 --- a/.github/workflows/periodic-vpp-verify-hst.yml +++ /dev/null @@ -1,186 +0,0 @@ -name: periodic-vpp-verify-hst - -on: - schedule: - - cron: '0 1 * * *' # Runs daily at 1am UTC - - cron: '0 13 * * *' # Runs daily at 1pm UTC - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -concurrency: - group: ${{ github.workflow }}-${{ inputs.FDIO_NAMESPACE }} - cancel-in-progress: true - -jobs: - verify-hst-periodic: - # yamllint disable-line rule:line-length - name: hst-${{ matrix.os }}-${{ matrix.executor_arch }}-${{ matrix.build_type }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:size=${{ matrix.size }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - include: - # Debug builds on aarch64 - - build_type: 'debug' - os: 'ubuntu2204' - executor_arch: 'aarch64' - class: 'hst' - size: 'hst' - # Debug builds on x86_64 - - build_type: 'debug' - os: 'ubuntu2204' - executor_arch: 'x86_64' - class: 'hst' - size: 'hst' - - env: - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - HS_TEST_DIR: /scratch/docker-build/vpp/test/hs-test - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: "auto" - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Set Timestamp and Log Path - run: | - set -euxo pipefail - ts="$(date -u +%Y_%m_%d_%H%M%S_UTC)" - LOG_NAME="gha-vpp-periodic/vpp-verify-hst-master-${ts}-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ env.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Pull VPP master HEAD - run: | - set -euxo pipefail - cd "$WORKSPACE" - git checkout master - git pull - git show --stat - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST VPP Build - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@3c0b690a135be65922fec705983478351a1b80b6 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - BUILD_HST: 'true' - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST Test VPP - if: success() - uses: fdio/vpp/.github/actions/vpp-hst-test@e970dcebe57999d0c04c63a466bb95604fcf9b4d - timeout-minutes: 90 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix['build_type'] }} - HS_TEST_DIR: ${{ env.HS_TEST_DIR }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/pr-verify.yml b/.github/workflows/pr-verify.yml new file mode 100644 index 000000000000..994d3b41c396 --- /dev/null +++ b/.github/workflows/pr-verify.yml @@ -0,0 +1,279 @@ +name: Verify pull request + +# yamllint disable-line rule:truthy +on: + pull_request: + branches: [master] + types: [opened, synchronize, reopened] + +# One run per ref: a new push to the same branch cancels the previous run. +concurrency: + group: pr-verify-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + style: + name: style + runs-on: ubuntu-24.04 + container: + image: debian:trixie + # A job that runs in a container gets `sh` for `run:` unless it says + # otherwise, and debian:trixie's /bin/sh is dash, which rejects the + # parameter expansions this repository's scripts use. + defaults: + run: + shell: bash + env: + # extras/scripts/checkstyle.sh picks clang-format-${CLANG_FORMAT_VER}; + # the Makefile sets 19 for Debian 13, and the same value has to reach the + # script when it is invoked directly. + CLANG_FORMAT_VER: "19" + steps: + - name: Disable APT cache cleanup + run: rm -f /etc/apt/apt.conf.d/docker-clean + + - name: Install base tools + run: | + apt-get update -qq + apt-get install -y -qq sudo make git bash + + - name: Check out the pull request + uses: actions/checkout@v4 + with: + # The coding style and API checks diff against a parent commit, and + # src/scripts/version reads the v* tags, so the whole history and + # tags are needed. + fetch-depth: 0 + fetch-tags: true + + - name: Install VPP checkstyle dependencies + run: | + git config --global --add safe.directory "$PWD" + # crcchecker.py refuses to run when a tracked file is modified, so + # this step must not rewrite the Makefile. + make UNATTENDED=yes install-dep + + - name: Check the feature list + run: make checkfeaturelist + + - name: Check the C coding style + # A pull_request checkout is the merge commit, whose first parent is + # the base branch tip, so HEAD~1..HEAD is the whole pull request. + run: make checkstyle + + - name: Check the API for incompatible changes + # crcchecker.py compares HEAD~1..HEAD as well, and refuses to run when + # the working tree is dirty, so this runs before the Python check + # builds anything. + run: make checkstyle-api + + - name: Check the Python coding style + run: make checkstyle-python + + - name: Check the commit messages of the pull request + run: | + set -eu + base="${{ github.event.pull_request.base.sha }}" + head="${{ github.event.pull_request.head.sha }}" + if [ -z "${base}" ] || [ -z "${head}" ]; then + make checkstyle-commit + exit 0 + fi + checked_out=$(git rev-parse HEAD) + trap 'git checkout --quiet "${checked_out}"' EXIT + # A merge commit has no feature id in its subject, and + # check_commit_msg.sh rejects a subject without one, so only the + # commits the pull request author wrote are checked. + for commit in $(git rev-list --reverse --no-merges "${base}..${head}"); do + echo "Checking commit message of ${commit}" + git checkout --quiet "${commit}" + extras/scripts/check_commit_msg.sh + done + + build-test: + name: build and test + runs-on: ubuntu-24.04 + container: + image: debian:trixie + # The test framework reserves SHM_PER_PROCESS per worker on top of a + # 1 GiB minimum; the 64 MiB container default is far too small. + options: --shm-size=2g + defaults: + run: + shell: bash + env: + CCACHE_COMPRESS: "true" + CCACHE_MAXSIZE: 2G + steps: + - name: Disable APT cache cleanup + run: rm -f /etc/apt/apt.conf.d/docker-clean + + - name: Install base tools + run: | + apt-get update -qq + apt-get install -y -qq sudo make git bash + + - name: Check out the pull request + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + # make install-dep installs $(DEB_DEPENDS), which is defined in the + # Makefile, so key the apt archive cache on that file: the key moves when + # the package list moves, and the run that first needs a new package is + # the run that stores it. The install step rewrites the Makefile + # afterwards, so the hash is taken into an output before that and both + # cache steps read the same value. + - name: Hash the dependency list + id: deps + run: | + git config --global --add safe.directory "$PWD" + echo "hash=$(git hash-object Makefile)" >> "$GITHUB_OUTPUT" + + - name: Restore apt archive cache + id: apt-cache + uses: actions/cache/restore@v4 + with: + path: /var/cache/apt/archives + key: apt-debian13-${{ runner.arch }}-${{ github.job }}-${{ steps.deps.outputs.hash }} + restore-keys: | + apt-debian13-${{ runner.arch }}-${{ github.job }}- + + - name: Install VPP build dependencies + run: | + sed -i '/^\s*git config commit\.template/s/^\(\s*\)/\1-/' Makefile + make UNATTENDED=yes install-dep + + # Saving is skipped when the exact key was restored: that entry already + # holds what this run would store, and asking to save a key that exists + # fails the step with "Unable to reserve cache". + - name: Save apt archive cache + if: steps.apt-cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: /var/cache/apt/archives + key: apt-debian13-${{ runner.arch }}-${{ github.job }}-${{ steps.deps.outputs.hash }} + + # Keyed by commit rather than by ref alone: a fixed key lets only the + # first build of a ref store it, and every later build is asked to save a + # key that already exists. A commit key gives each build its own entry, + # and the prefix in restore-keys still lends a run the newest entry of + # another commit on the same ref. + - name: Restore ccache + id: ccache + uses: actions/cache/restore@v4 + with: + path: build-root/.ccache + key: ccache-debug-debian13-${{ runner.arch }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ccache-debug-debian13-${{ runner.arch }}-${{ github.ref_name }}- + ccache-debug-debian13-${{ runner.arch }}- + + # src/scripts/version calls git describe without --tags, and origin + # (sunoaki/vpp) has no v* tags, so the version is empty and the CMake + # configure aborts with "string sub-command REPLACE requires at least + # four arguments". Synthesise a version in the same format the DEB + # workflow uses, whose ordering is already thought through, whenever no + # tag is reachable. + - name: Provide a version when origin has no tag + run: | + if ! git describe --tags --long --match 'v[0-9]*' 2>/dev/null; then + echo "0.0.0-release+$(git log -1 --format=%ct).$(git rev-list --count HEAD)-g$(git rev-parse --short=7 HEAD)" \ + > src/scripts/.version + fi + + - name: Build VPP debug + run: | + git config --global --add safe.directory "$PWD" + export VPP_EXTRA_CMAKE_ARGS="\ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache" + ccache -z + make UNATTENDED=yes build + ccache -s + + - name: Save ccache + if: steps.ccache.outputs.cache-hit != 'true' + uses: actions/cache/save@v4 + with: + path: build-root/.ccache + key: ccache-debug-debian13-${{ runner.arch }}-${{ github.ref_name }}-${{ github.sha }} + + # Turn the changed paths into a focused TEST filter. Every emitted name + # must have an exact test_.py module, because the runner resolves a + # bare filter to that file name and matches it literally: a name with no + # such module selects no test and fails the job instead of the suite + # falling back. For src/plugins///... the subdirectory is tried + # first, so src/plugins/nat/nat64/nat64_db.c selects nat64 (test_nat64.py) + # rather than the package name nat, which has no module of its own and + # would match nothing. src/vnet/ selects only on an exact + # module too, because a prefix match on a directory such as ip would pull + # in every ip4/ip6/ipip/ipsec suite. Changes under src/vppinfra, src/svm + # and src/vlib select nothing here and use the fallback smoke suite. + - name: Derive a test filter from the changed paths + id: tests + run: | + set -eu + base="${{ github.event.pull_request.base.sha }}" + head="${{ github.event.pull_request.head.sha }}" + if [ -z "${base}" ] || [ -z "${head}" ]; then + base=$(git rev-parse HEAD^ 2>/dev/null || echo "") + head=$(git rev-parse HEAD) + fi + changed=$(git diff --name-only "${base}...${head}" 2>/dev/null || + git diff --name-only "${base}..${head}" 2>/dev/null || true) + suites="" + for path in ${changed}; do + name="" + case "${path}" in + src/plugins/*/*) + rest=${path#src/plugins/} + dir=${rest%%/*} + sub=${rest#*/} + sub=${sub%%/*} + for candidate in "${sub}" "${dir}"; do + candidate=${candidate//-/_} + if [ -f "test/test_${candidate}.py" ]; then + name=${candidate} + break + fi + done + ;; + src/vnet/*/*) + name=${path#src/vnet/} + name=${name%%/*} + name=${name//-/_} + [ -f "test/test_${name}.py" ] || name="" + ;; + esac + [ -n "${name}" ] || continue + suites="${suites} ${name}" + done + suites=$(echo "${suites}" | tr ' ' '\n' | sed '/^$/d' | + sort -u | head -n 3 | tr '\n' ' ') + if [ -z "${suites}" ]; then + echo "No test derives from the changed paths; running the ip4 smoke suite" + suites="ip4" + fi + echo "Running TEST suites:${suites}" + echo "suites=${suites}" >> "$GITHUB_OUTPUT" + + - name: Remount /dev/shm for the test harness + uses: ./.github/actions/vpp-docker-runtime-setup + with: + WORKSPACE: ${{ github.workspace }} + SHM_SIZE: 2G + LOG_DIR: ${{ github.workspace }}/build-root/test-logs + + - name: Run the Python functional tests + run: | + git config --global --add safe.directory "$PWD" + set -eu + for suite in ${{ steps.tests.outputs.suites }}; do + echo "Running TEST=${suite}" + make UNATTENDED=yes TEST_JOBS=auto RETRIES=2 TEST="${suite}" test-debug + done diff --git a/.github/workflows/vpp-csit-verify-api.yml b/.github/workflows/vpp-csit-verify-api.yml deleted file mode 100644 index 4eaccf726b93..000000000000 --- a/.github/workflows/vpp-csit-verify-api.yml +++ /dev/null @@ -1,185 +0,0 @@ - -name: vpp-csit-verify-api - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - csit-verify-api: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:size=${{ matrix.size }} - - fdio:namespace=${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - size: ['csit'] - - env: - WORKSPACE: /scratch/docker-build/vpp - CSIT_DIR: /scratch/docker-build/csit - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - FDIO_NAMESPACE: ${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-csit-api-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ github.event.inputs.FDIO_NAMESPACE || 'prod' }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: "Show Nomad Dockerfile" - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Run CSIT check_crc.sh - run: | - set -euxo pipefail - { - # Checkout CSIT HEAD - cd "$CSIT_DIR" - git checkout HEAD - - # Run check_crc.sh from CSIT - cd "$WORKSPACE" - # TODO: fix csit bash scripts to not rely on CSIT_DIR being inside WORKSPACE - NEW_CSIT_DIR="$WORKSPACE/$(basename "$CSIT_DIR")" - mv "$CSIT_DIR" "$NEW_CSIT_DIR" - csit_entry_dir="$NEW_CSIT_DIR/resources/libraries/bash/entry" - source "${csit_entry_dir}/with_oper_for_vpp.sh" "check_crc.sh" - } | tee "$LOG_DIR/csit_api.log" - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-merge-docs.yml b/.github/workflows/vpp-merge-docs.yml deleted file mode 100644 index ec5bf2b0b0c3..000000000000 --- a/.github/workflows/vpp-merge-docs.yml +++ /dev/null @@ -1,215 +0,0 @@ -name: vpp-merge-docs - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - merge-docs: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/merge-docs-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - S3_DOCS_PATH: "" - S3_DOCS_URL: https://s3-docs.fd.io - WORKSPACE: /scratch/docker-build/vpp - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - vpp_release="$(${WORKSPACE}/build-root/scripts/version rpm-version)" - echo "S3_DOCS_PATH=vpp/$vpp_release" >> "$GITHUB_ENV" - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Make Docs - id: make-docs - run: | - set -euxo pipefail - cd "$WORKSPACE" - make docs | tee "$LOG_DIR/make_docs.log" - - - name: AWS S3 Publish Docs - if: ${{ steps.make-docs.outcome == 'success' && inputs.FDIO_NAMESPACE == 'prod' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_DOCS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_DOCS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_DOCS_BUCKET }} - s3_path: ${{ env.S3_DOCS_PATH }} - s3_url: ${{ env.S3_DOCS_URL }} - jenkins_hostname: "" - archives_path: "${{ env.WORKSPACE }}/build-root/docs/html" - compress_files: false - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-merge-maketest.yml b/.github/workflows/vpp-merge-maketest.yml deleted file mode 100644 index 06fe376be6f5..000000000000 --- a/.github/workflows/vpp-merge-maketest.yml +++ /dev/null @@ -1,330 +0,0 @@ -name: vpp-merge-maketest - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - merge-maketest: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=builder - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - os: ['ubuntu2404', 'ubuntu2204', 'debian12'] - executor_arch: ['x86_64', 'aarch64'] - build_type: ['release'] - exclude: - # Exclude debian12 on aarch64 for all builds - - os: 'debian12' - executor_arch: 'aarch64' - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/merge-maketest-${{ matrix.build_type }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - BUILD_TYPE: ${{ matrix.build_type }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: 4 - TEST_RETRIES: 3 - MAKE_TEST_OS: ${{ matrix.os == 'ubuntu2204' && 'ubuntu-22.04' || 'ubuntu-24.04' }} - MAKE_TEST_MULTIWORKER_OS: 'debian-12' - VPPAPIGEN_TEST_OS: ${{ matrix.os == 'ubuntu2204' && 'ubuntu-22.04' || 'ubuntu-24.04' }} - SHM_SIZE: ${{ matrix.executor_arch == 'aarch64' && '4096M' || '2048M' }} - TUI_LINE: "*******************************************************************" - VPP_TEARDOWN_TIMEOUT: 30 - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Build VPP Packages - run: | - set -euxo pipefail - { - cd "$WORKSPACE" - # pkg-verify runs install-deps, install-ext-deps, and builds vpp packages - make UNATTENDED=yes pkg-verify - # force rebuild of vpp-opt-deps - apt-get remove -y vpp-opt-deps || true - make UNATTENDED=yes install-opt-deps || true - } | tee "$LOG_DIR/build-all-packages.log" - - - name: Push Packages to Packagecloud - if: ${{ always() && inputs.FDIO_NAMESPACE == 'prod' }} - run: | - set -euxo pipefail - pkgcloud_cmd="" - vpp_pkgs="" - - create_deb_push_cmds() - { - local distro="$(/usr/bin/facter operatingsystem)" - local codename="$(/usr/bin/facter lsbdistcodename)" - local downloads_dir="/root/Downloads" - local pkgcloud_acct="fdio" - - echo '{"url":"https://packagecloud.io","token":"${{ secrets.PACKAGECLOUD_TOKEN }}"}' > /root/.packagecloud - distro=${distro,,} # to lowercase - codename=${codename,,} # to lowercase - pkgcloud_cmd="package_cloud push ${pkgcloud_acct}/${GERRIT_BRANCH/stable\//}/${distro}/${codename}/main/ " - if [ "$distro" = "debian" ] || [ "$distro" = "ubuntu" ] ; then - vpp_pkgs="$(find . -type f -iname '*.deb' | xargs || true)" - if [ -z "$vpp_pkgs" ] ; then - echo -e "\n$TUI_LINE\n* ERROR: No .deb packages found to push\n* PACKAGECLOUD PUSH FAILED!\n$TUI_LINE\n" - exit 1 - fi - else - echo -e "\n$TUI_LINE\n* ERROR: Unknown distro: '$distro'\n* PACKAGECLOUD PUSH FAILED!\n$TUI_LINE\n" - exit 1 - fi - } - - if [ -f ~/.packagecloud ]; then - cd "$WORKSPACE" - create_deb_push_cmds - for pkg in $vpp_pkgs ; do - $pkgcloud_cmd $pkg || true - done - else - echo -e "\n$TUI_LINE\n* ERROR: Missing '~/.packagecloud' for user '$(id)'\n* PACKAGECLOUD PUSH FAILED!\n$TUI_LINE\n" - exit 1 - fi - echo -e "\n$TUI_LINE\n* PACKAGECLOUD PUSH COMPLETE\n$TUI_LINE\n" - - - name: Post Packagecloud Push - if: ${{ always() && inputs.FDIO_NAMESPACE == 'prod' }} - run: | - echo '{"url":"https://packagecloud.io","token":"$token"}' > /root/.packagecloud - - - name: Remove vpp-opt-deps Before Make Test - if: always() - run: | - set -euxo pipefail - apt-get remove -y vpp-opt-deps || true - - - name: Setup Docker VPP Runtime Environment for 'make test' - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-docker-runtime-setup@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - SHM_SIZE: ${{ env.SHM_SIZE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: VPP Make Test - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-make-test@03f35503fee57df2348617498db28fde557a3880 - timeout-minutes: 120 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix.build_type }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TEST_RETRIES: ${{ env.TEST_RETRIES }} - MAKE_TEST_OS: ${{ env.MAKE_TEST_OS }} - MAKE_TEST_MULTIWORKER_OS: ${{ env.MAKE_TEST_MULTIWORKER_OS }} - VPPAPIGEN_TEST_OS: ${{ env.VPPAPIGEN_TEST_OS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - unittest_failed_dir="/tmp/vpp-failed-unittests" - for failed_test in $(ls "$unittest_failed_dir") - do - unittest_failed_test="$unittest_failed_dir"/"$failed_test" - if [ -f "$unittest_failed_test" ] ; then - cp -a "$unittest_failed_test" "$archives_dir" - else - failed_test_dir="$archives_dir"/"$failed_test" - if [ ! -e "$failed_test_dir" ] ; then - mkdir -p "$failed_test_dir" - cp -a "$unittest_failed_test"/* "$failed_test_dir" - fi - fi - done - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/vpp-verify-arm-drivers.yml b/.github/workflows/vpp-verify-arm-drivers.yml deleted file mode 100644 index 68644e4e883f..000000000000 --- a/.github/workflows/vpp-verify-arm-drivers.yml +++ /dev/null @@ -1,224 +0,0 @@ -name: vpp-verify-arm-drivers - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-arm-drivers: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['aarch64'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-arm-drivers-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - ls -l "$LOG_DIR" - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Build VPP ARM Drivers - run: | - set -euxo pipefail - cd "$WORKSPACE" - ls -l "$LOG_DIR" - BUILD_RESULT="* SUCCESSFULLY COMPLETED" - BUILD_ERROR="" - RETVAL="0" - make_pkg_verify_arm_driver() { - vpp_platform="$1" - git clean -fdx build build-root src test || true - if ! make UNATTENDED=yes pkg-verify VPP_PLATFORM="$vpp_platform" ; then - BUILD_ERROR+="* FAILED 'make UNATTENDED=yes pkg-verify VPP_PLATFORM=$vpp_platform'\n" - fi - } - if [ -f $WORKSPACE/extras/scripts/build_static_vppctl.sh ] ; then - if ! $WORKSPACE/extras/scripts/build_static_vppctl.sh ; then - BUILD_ERROR="* FAILED '$WORKSPACE/extras/scripts/build_static_vppctl.sh'\n" - fi - fi - make_pkg_verify_arm_driver cn913x | tee "$LOG_DIR/arm_driver_build_cn913x.log" - make_pkg_verify_arm_driver octeon9 | tee "$LOG_DIR/arm_driver_build_octeon9.log" - make_pkg_verify_arm_driver octeon10 | tee "$LOG_DIR/arm_driver_build_octeon10.log" - { - if [ -n "$BUILD_ERROR" ] ; then - BUILD_RESULT="$BUILD_ERROR" - RETVAL="1" - fi - echo -e "\n$TUI_LINE\n* VPP ${OS_ID^^}-${OS_VERSION_ID}-${OS_ARCH^^} ARM DRIVERS BUILD\n$BUILD_RESULT\n$TUI_LINE\n" - } | tee "$LOG_DIR/arm_driver_build_results.log" - exit "$RETVAL" - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-verify-checkstyle.yml b/.github/workflows/vpp-verify-checkstyle.yml deleted file mode 100644 index 6bd6ecd8bda2..000000000000 --- a/.github/workflows/vpp-verify-checkstyle.yml +++ /dev/null @@ -1,266 +0,0 @@ -name: vpp-verify-checkstyle - -# yamllint disable-line rule:truthy -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-checkstyle: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['release'] - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_ID: ${{ inputs.GERRIT_CHANGE_ID }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_EVENT_TYPE: ${{ inputs.GERRIT_EVENT_TYPE }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PATCHSET_REVISION: ${{ inputs.GERRIT_PATCHSET_REVISION }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - WORKSPACE: /scratch/docker-build/vpp - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-checkstyle-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - TUI_LINE: "*******************************************************************" - - steps: - - name: Setup Environment - id: setup-executor-env - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: "Show Nomad Dockerfile" - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Run Checkstyle Scripts - run: | - set -euxo pipefail - cd $WORKSPACE - { - BUILD_EXT_DIR="build/external" - MATRIX_FILE="$BUILD_EXT_DIR/mlx_rdma_dpdk_matrix.txt" - PKGS_DIR="$BUILD_EXT_DIR/packages" - - if git show --stat | grep -q "$MATRIX_FILE" ; then - RDMA_CORE_VERSION="$(grep rdma-core_version $PKGS_DIR/rdma-core.mk | grep -v '(' | mawk '{print $3}')" - DPDK_VERSION="$(grep dpdk_version $PKGS_DIR/dpdk.mk | grep -v '(' | mawk '{print $3}')" - CURRENT_MATRIX="rdma=$RDMA_CORE_VERSION dpdk=$DPDK_VERSION" - if grep -q "$CURRENT_MATRIX" "$MATRIX_FILE"; then - echo -e "$TUI_LINE\n* DPDK/RDMA-CORE matrix file update successfully verified\n$TUI_LINE" - else - echo -e "$TUI_LINE\n* ERROR: 'rdma=$RDMA_CORE_VERSION dpdk=$DPDK_VERSION' not found in $MATRIX_FILE!\n$TUI_LINE" - exit 1 - fi - else - echo -e "$TUI_LINE\n* DPDK/RDMA-CORE matrix file not modified in $GERRIT_CHANGE_URL \n$TUI_LINE" - fi - } | tee $LOG_DIR/dpdk_rdma_matrix_check.log - - make UNATTENDED=yes checkstyle | tee $LOG_DIR/make_checkstyle.log - - - name: Run Test Checkstyle Scripts - run: | - set -euxo pipefail - cd $WORKSPACE - make test-checkstyle | tee $LOG_DIR/test_checkstyle.log - - - name: Run API Checkstyle Scripts - run: | - set -euxo pipefail - cd $WORKSPACE - { - VPP_CRC_CHECKER="extras/scripts/crcchecker.py" - VPP_CRC_CHECKER_CMD="$VPP_CRC_CHECKER --check-patchset" - - # API checker complains if the git repo is not clean. - # Help diagnosing those issues easier - git --no-pager diff - echo "Running $VPP_CRC_CHECKER_CMD" - if $VPP_CRC_CHECKER_CMD; then - echo "API check successful" - else - RET_CODE=$? - echo "API check failed: ret code $RET_CODE; please read https://wiki.fd.io/view/VPP/ApiChangeProcess and discuss with ayourtch@gmail.com if unsure how to proceed" - echo "::error file=.ci/scripts/vpp/crcchecker.py,line=1::API check FAILED for $GITHUB_REF. See run: https://github.com/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" - exit $RET_CODE - fi - } | tee $LOG_DIR/api_checkstyle.log - - - name: Run Commit Message Checkstyle - run: | - set -euxo pipefail - cd $WORKSPACE - { - echo "Running extras/scripts/check_commit_msg.sh" - extras/scripts/check_commit_msg.sh - } | tee $LOG_DIR/commit_checkstyle.log - - - name: Run Docs Spell - run: | - set -euxo pipefail - cd $WORKSPACE - make docs-spell | tee $LOG_DIR/docs_spelling_checkstyle.log - - - name: AWS S3 Publish Logs - if: ${{ always() }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-verify-docs.yml b/.github/workflows/vpp-verify-docs.yml deleted file mode 100644 index a1a10f87c4ce..000000000000 --- a/.github/workflows/vpp-verify-docs.yml +++ /dev/null @@ -1,200 +0,0 @@ -name: vpp-verify-docs - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-docs: - # yamllint disable-line rule:line-length - name: ${{ inputs.GERRIT_CHANGE_NUMBER }}/${{ inputs.GERRIT_PATCHSET_NUMBER}}__${{ inputs.GERRIT_BRANCH }}__${{ inputs.FDIO_NAMESPACE }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - class: ['builder'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "" - LOG_DIR: "" - S3_DOCS_7DAY_PATH: "" - S3_DOCS_7DAY_URL: https://s3-docs-7day.fd.io/vex-yul-rot-jenkins-1 - WORKSPACE: /scratch/docker-build/vpp - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - LOG_NAME="${{ env.LOG_PREFIX }}/verify-docs-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - echo "S3_DOCS_7DAY_PATH=$LOG_NAME/docs" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Make Docs - id: make-docs - run: | - set -euxo pipefail - cd "$WORKSPACE" - make docs | tee "$LOG_DIR/make_docs.log" - - - name: AWS S3 Publish Docs - if: ${{ steps.make-docs.outcome == 'success' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_DOCS_7DAY_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_DOCS_7DAY_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_DOCS_7DAY_BUCKET}} - s3_path: ${{ env.S3_DOCS_7DAY_PATH }} - s3_url: ${{ env.S3_DOCS_7DAY_URL }} - archives_path: "${{ env.WORKSPACE }}/build-root/docs/html" - compress_files: false - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-verify-gcc.yml b/.github/workflows/vpp-verify-gcc.yml deleted file mode 100644 index 051689daca4e..000000000000 --- a/.github/workflows/vpp-verify-gcc.yml +++ /dev/null @@ -1,217 +0,0 @@ -name: vpp-verify-gcc - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-gcc: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=builder - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - build_type: ['debug', 'release'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-gcc-${{ matrix.build_type }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TUI_LINE: "*******************************************************************" - VPP_TEARDOWN_TIMEOUT: 30 - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Build VPP with GCC - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@3c0b690a135be65922fec705983478351a1b80b6 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - USE_GCC: "true" - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} diff --git a/.github/workflows/vpp-verify-hst-u2204.yml b/.github/workflows/vpp-verify-hst-u2204.yml deleted file mode 100644 index bfb5479c07bf..000000000000 --- a/.github/workflows/vpp-verify-hst-u2204.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: vpp-verify-hst-u2204 - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-hst: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:size=${{ matrix.size }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['debug'] - class: ['hst'] - os: ['ubuntu2204'] - executor_arch: ['x86_64', 'aarch64'] - size: ['hst'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-hst-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - WORKSPACE: /scratch/docker-build/vpp - HS_TEST_DIR: /scratch/docker-build/vpp/test/hs-test - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: "auto" - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST VPP Build - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@3c0b690a135be65922fec705983478351a1b80b6 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - BUILD_HST: 'true' - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST Test VPP - if: success() - uses: fdio/vpp/.github/actions/vpp-hst-test@e970dcebe57999d0c04c63a466bb95604fcf9b4d - timeout-minutes: 90 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix['build_type'] }} - HS_TEST_DIR: ${{ env.HS_TEST_DIR }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/vpp-verify-hst.yml b/.github/workflows/vpp-verify-hst.yml deleted file mode 100644 index f9ea8da08181..000000000000 --- a/.github/workflows/vpp-verify-hst.yml +++ /dev/null @@ -1,252 +0,0 @@ -name: vpp-verify-hst - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-hst: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:size=${{ matrix.size }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - build_type: ['debug', 'release'] - class: ['hst'] - os: ['ubuntu2404'] - executor_arch: ['x86_64'] - size: ['hst'] - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-hst-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - WORKSPACE: /scratch/docker-build/vpp - HS_TEST_DIR: /scratch/docker-build/vpp/test/hs-test - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: "auto" - TUI_LINE: "*******************************************************************" - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST VPP Build - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@3c0b690a135be65922fec705983478351a1b80b6 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - BUILD_HST: 'true' - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Start Docker Daemon - uses: fdio/vpp/.github/actions/vpp-start-docker@f9e0f384d665800c8e580da853e26558dd6d2668 - with: - START_DOCKER_SCRIPT: start-docker.sh - TUI_LINE: ${{ env.TUI_LINE }} - - - name: HST Test VPP - if: success() - uses: fdio/vpp/.github/actions/vpp-hst-test@e970dcebe57999d0c04c63a466bb95604fcf9b4d - timeout-minutes: 90 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix['build_type'] }} - HS_TEST_DIR: ${{ env.HS_TEST_DIR }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.github/workflows/vpp-verify-maketest.yml b/.github/workflows/vpp-verify-maketest.yml deleted file mode 100644 index 59f80b772c47..000000000000 --- a/.github/workflows/vpp-verify-maketest.yml +++ /dev/null @@ -1,285 +0,0 @@ -name: vpp-verify-maketest - -on: - workflow_call: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: true - type: string - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - workflow_dispatch: - inputs: - FDIO_NAMESPACE: - description: "Executor Namespace (prod|sandbox)" - required: false - type: choice - options: - - sandbox - - prod - default: prod - GERRIT_BRANCH: - description: "Branch that change is against" - required: true - type: string - GERRIT_CHANGE_NUMBER: - description: "The Gerrit number" - required: true - type: string - GERRIT_PATCHSET_NUMBER: - description: "The patch number for the change" - required: true - type: string - GERRIT_CHANGE_ID: - description: "The ID for the change" - required: true - type: string - GERRIT_CHANGE_URL: - description: "URL to the change" - required: true - type: string - GERRIT_EVENT_TYPE: - description: "Type of Gerrit event" - required: true - type: string - GERRIT_PATCHSET_REVISION: - description: "The revision sha" - required: true - type: string - GERRIT_PROJECT: - description: "Project in Gerrit" - required: true - type: string - GERRIT_REFSPEC: - description: "Gerrit refspec of change" - required: true - type: string - LOG_PREFIX: - description: "Prefix of the log directory" - required: true - type: string - -defaults: - run: - shell: bash - -permissions: - contents: read - actions: read - -jobs: - verify-maketest: - name: ${{ matrix.build_type }}_${{ matrix.os }}_${{ matrix.executor_arch }} - runs-on: - - self-hosted - - nomad - - fdio:arch=${{ matrix.executor_arch }} - - fdio:class=${{ matrix.class }} - - fdio:namespace=${{ inputs.FDIO_NAMESPACE || 'prod' }} - - fdio:os=${{ matrix.os }} - - fdio:workflow=${{ github.run_id }} - - strategy: - fail-fast: false - matrix: - os: ['ubuntu2404', 'ubuntu2204', 'debian12'] - executor_arch: ['x86_64', 'aarch64'] - class: ['builder'] - build_type: ['debug', 'release'] - exclude: - # Exclude debian12 on aarch64 for all builds - - os: 'debian12' - executor_arch: 'aarch64' - # Exclude non-ubuntu2204 OS for debug builds - - build_type: 'debug' - os: 'ubuntu2404' - - build_type: 'debug' - os: 'debian12' - # Exclude aarch64 architecture for debug builds - - build_type: 'debug' - executor_arch: 'aarch64' - - env: - FDIO_NAMESPACE: ${{ inputs.FDIO_NAMESPACE }} - GERRIT_BRANCH: ${{ inputs.GERRIT_BRANCH }} - GERRIT_CHANGE_URL: ${{ inputs.GERRIT_CHANGE_URL }} - GERRIT_CHANGE_NUMBER: ${{ inputs.GERRIT_CHANGE_NUMBER }} - GERRIT_PATCHSET_NUMBER: ${{ inputs.GERRIT_PATCHSET_NUMBER }} - GERRIT_PROJECT: ${{ inputs.GERRIT_PROJECT }} - GERRIT_REFSPEC: ${{ inputs.GERRIT_REFSPEC }} - LOG_PREFIX: ${{ inputs.LOG_PREFIX }} - LOG_NAME: "${{ inputs.LOG_PREFIX }}/verify-maketest-${{ matrix.build_type }}-${{ matrix.class }}-${{ matrix.os }}-${{ inputs.FDIO_NAMESPACE }}-${{ matrix.executor_arch }}" - LOG_DIR: "" - WORKSPACE: /scratch/docker-build/vpp - CCACHE_DIR: /scratch/ccache/${{ matrix.os }}-${{ matrix.executor_arch }} - MAKE_PARALLEL_JOBS: 16 - TEST_JOBS: 4 - TEST_RETRIES: 3 - MAKE_TEST_OS: ${{ matrix.os == 'ubuntu2204' && 'ubuntu-22.04' || 'ubuntu-24.04' }} - MAKE_TEST_MULTIWORKER_OS: 'debian-12' - VPPAPIGEN_TEST_OS: ${{ matrix.os == 'ubuntu2204' && 'ubuntu-22.04' || 'ubuntu-24.04' }} - SHM_SIZE: ${{ matrix.executor_arch == 'aarch64' && '4096M' || '2048M' }} - TUI_LINE: "*******************************************************************" - VPP_TEARDOWN_TIMEOUT: 30 - - steps: - - name: Actions Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 # v6.0.2 - - - name: Setup Environment - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/setup-executor-env@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Prepare Log Directory - run: | - set -euxo pipefail - echo "LOG_NAME=$LOG_NAME" >> "$GITHUB_ENV" - LOG_DIR="$WORKSPACE/logs/$LOG_NAME" - echo "LOG_DIR=$LOG_DIR" >> "$GITHUB_ENV" - mkdir -p "$LOG_DIR" - - - name: Show Environment - run: | - set -euo pipefail - env | sort | tee "$LOG_DIR/environment.log" - - - name: Show Nomad Dockerfile - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/show-nomad-dockerfile@b728d1589228e2891a4241a1bba362d76895c225 - - - name: Checkout VPP Gerrit Change - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-checkout-gerrit-change@27fc2aa27f57c8f7055e8f1b6e5a9f41b0315873 - with: - WORKSPACE: ${{ env.WORKSPACE }} - GERRIT_BRANCH: ${{ env.GERRIT_BRANCH }} - GERRIT_REFSPEC: ${{ env.GERRIT_REFSPEC }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-deps@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Install VPP external dependencies - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-install-ext-deps@d0577011fb512d79e6914b6f1f11e2be4c09bc1e - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Build VPP - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-build@3c0b690a135be65922fec705983478351a1b80b6 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - MAKE_PARALLEL_JOBS: ${{ env.MAKE_PARALLEL_JOBS }} - BUILD_TYPE: ${{ matrix['build_type'] }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: Setup Docker VPP Runtime Environment for 'make test' - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-docker-runtime-setup@777792ecef2d5c93ce15fa5b7fdca999f5412c9d - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - SHM_SIZE: ${{ env.SHM_SIZE }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: VPP Make Test - # yamllint disable-line rule:line-length - uses: fdio/vpp/.github/actions/vpp-make-test@03f35503fee57df2348617498db28fde557a3880 - timeout-minutes: 120 - with: - LOG_DIR: ${{ env.LOG_DIR }} - WORKSPACE: ${{ env.WORKSPACE }} - BUILD_TYPE: ${{ matrix.build_type }} - TEST_JOBS: ${{ env.TEST_JOBS }} - TEST_RETRIES: ${{ env.TEST_RETRIES }} - MAKE_TEST_OS: ${{ env.MAKE_TEST_OS }} - MAKE_TEST_MULTIWORKER_OS: ${{ env.MAKE_TEST_MULTIWORKER_OS }} - VPPAPIGEN_TEST_OS: ${{ env.VPPAPIGEN_TEST_OS }} - TUI_LINE: ${{ env.TUI_LINE }} - - - name: AWS S3 Publish Logs - if: always() - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.LOG_DIR }} - - - name: Check Archives Directory - id: check-archives - if: always() - run: | - set -euxo pipefail - archives_dir="$WORKSPACE/archives" - unittest_failed_dir="/tmp/vpp-failed-unittests" - for failed_test in $(ls "$unittest_failed_dir") - do - unittest_failed_test="$unittest_failed_dir"/"$failed_test" - if [ -f "$unittest_failed_test" ] ; then - cp -a "$unittest_failed_test" "$archives_dir" - else - failed_test_dir="$archives_dir"/"$failed_test" - if [ ! -e "$failed_test_dir" ] ; then - mkdir -p "$failed_test_dir" - cp -a "$unittest_failed_test"/* "$failed_test_dir" - fi - fi - done - if [[ -d "$archives_dir" ]] && find "$archives_dir" -type f -print -quit | grep -q . ; then - echo "has_archives=true" >> "$GITHUB_OUTPUT" - else - echo "has_archives=false" >> "$GITHUB_OUTPUT" - fi - - - name: AWS S3 Publish Archives - if: ${{ always() && steps.check-archives.outputs.has_archives == 'true' }} - # yamllint disable-line rule:line-length - uses: fdio/.github/.github/actions/aws-s3-publish-logs@1b8eff3ffd9d355b28052be9ad22acc57b3430b5 - with: - aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws_region: ${{ vars.AWS_REGION }} - s3_bucket: ${{ vars.S3_BUCKET }} - s3_path: ${{ env.LOG_NAME }} - archives_path: ${{ env.WORKSPACE }}/archives diff --git a/.gitignore b/.gitignore index d546f9954b5a..c848588d62e0 100644 --- a/.gitignore +++ b/.gitignore @@ -189,3 +189,7 @@ calicovppversion # log file archives /archives + +# Agent workspace metadata +/.omo/ +/.sisyphus/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..3ab84ed6734f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,664 @@ +# AGENTS.md — Working on this VPP tree + +This file is for AI coding agents (and humans) who know nothing about this +project. It describes what the repository is, how it is built, how it is +tested, and the conventions that changes are expected to follow. + +--- + +## 1. Project overview + +This is **FD.io VPP (Vector Packet Processing)**, the open-source version of +Cisco's VPP technology: a high-performance, production-quality packet +processing stack that runs on commodity CPUs. It is the dataplane framework +behind the FD.io project, licensed under **Apache-2.0**. + +VPP processes packets as a **directed graph of forwarding nodes**. Each node +receives a *frame* of buffers (vector) and processes them in a tight loop, +which enables prefetching, pipelining, and SIMD. Multiple worker threads run +replicas of the graph, with ingress-hashed packet steering. + +**This checkout is a fork.** The important remotes: + +| Remote | URL | Meaning | +| ---------- | ---------------------------- | ---------------------------------------- | +| `origin` | `git@github.com:sunoaki/vpp.git` | This fork (the one being developed here) | +| `upstream` | `git@github.com:FDio/vpp.git` | Canonical upstream FD.io VPP | + +- The current branch is `master`. +- `master` is **60 commits ahead of `upstream/master`**. These commits are + hardening/fix work (see "Fork-specific context" below), not upstream changes. +- Version string comes from `src/scripts/version` / `git describe`; as of this + writing it is `v26.10-rc0-489-gbc8310875`. +- Project metadata lives in `INFO.yaml`; the project PTL and committer list are + there and in `MAINTAINERS`. + +Upstream project resources (useful when a change may need to go upstream): +Gerrit `gerrit.fd.io` (`project=vpp`, see `.gitreview`), GitHub issues at +`https://github.com/fdio/vpp/issues`, mailing list `vpp-dev@lists.fd.io`. + +--- + +## 2. Repository layout + +| Path | Contents | +| -------------------- | ------------------------------------------------------------------------ | +| `src/vppinfra` | Core infrastructure library: vectors, pools, hashes/bihash, bitmaps, time, format/unformat, allocators, SIMD helpers | +| `src/svm` | Shared virtual memory allocation library (used for API shared memory) | +| `src/vlib` | Vector processing library: buffer & graph-node management, threads, CLI, counters, tracing, event logger | +| `src/vlibmemory` | Shared-memory / API memory management (client & server sides) | +| `src/vlibapi` | Binary API library | +| `src/vnet` | The network stack: L2/L3/L4 nodes, interfaces, FIB, features, sessions | +| `src/vpp` | The `vpp` application container (links everything), `vppctl`, `vpe.api` | +| `src/vpp-api` | API client bindings: C (`client/`), C++/C (`vapi/`), Python (`python/vpp_papi`), Lua | +| `src/vat`, `src/vat2`| API test programs / vat2 plugin framework | +| `src/vcl` | VPP Communications Library and LD_PRELOAD shim (`vppcom`, `vcl_ldpreload`) | +| `src/plugins` | ~99 bundled dataplane plugins (see below) | +| `src/drivers` | Built-in device drivers (armada, atlantic, iavf, ige, octeon) | +| `src/crypto_engines` | Crypto backends: `ipsecmb`, `native`, `openssl` | +| `src/tools` | `vppapigen` (API compiler), `g2`, `perftool`, `appimage` | +| `src/scripts` | Version/utility scripts, including `src/scripts/version` | +| `src/cmake` | CMake rule/function definitions and `platform/*.cmake` CPU tuning files | +| `test` | Python unit/functional test harness (`VppTestCase`) + `test/asf` + `test/hs-test` | +| `test/hs-test` | Go/Ginkgo host-stack end-to-end test framework (Docker topologies) | +| `extras` | Packaging (`deb`, `rpm`, `snap`), Vagrant, emacs plugin generator, libmemif, config tools, kube-test | +| `docs` | Sphinx documentation tree (`.rst`/`.md`, symlinks into `src/`) | +| `build-data` | Build metadata: `packages/*.mk`, `platforms/*.mk` | +| `build-root` | Build output directory (generated; largely gitignored) | +| `build`, `build-data`, `build-root` | See build system section | + +`README.md` documents the directory layout in the project's own words. +`MAINTAINERS` is authoritative for who owns a component and for the feature-id +vocabulary used by commit-message checks. + +### The plugins directory + +`src/plugins` contains ~99 subdirectories, each a shared object +(`_plugin.so`) built from its own `CMakeLists.txt`. There is no central +registration list: `src/plugins/CMakeLists.txt` globs `*/CMakeLists.txt` and +adds each subdirectory, and each plugin calls `add_vpp_plugin( ...)`. +Plugin selection can be constrained at configure time with +`-DVPP_PLUGINS=` or `-DVPP_EXCLUDED_PLUGINS=`. + +Representative plugins: `acl`, `nat`, `ipsec`, `dpdk`, `rdma`, `memif`, +`linux-cp`, `gre`, `quic`, `http_static`, `srv6-*`, `lisp`, `flowprobe`, +`sfdp*`, `lacp`, `bonding`, `pppoe`, `gtpu`, `map`, `nsh`, `vhost`, +`af_packet`, `af_xdp`, `netmap`. + +Note that not every protocol/feature is a plugin: `session`, `tls`, `tcp`, +`udp`, `vxlan`, `ipsec`, `fib`, `ip`, `ip6` and others live in `src/vnet`, +while `src/plugins` holds the separately-loadable `.so` modules. + +--- + +## 3. Technology stack + +- **Language:** C (C11-style, GNU extensions). Some C++ in `src/vpp-api/vapi` + (`vapi.hpp`, `vapi_cpp_test.cpp`) and `src/plugins/*/` C++ usages. +- **Test/aux languages:** Python 3 (functional test harness, docs, tooling), + Go (hs-test, kube-test), shell. +- **Build system:** top-level GNU `Makefile` → `build-root/Makefile` → + **CMake (>= 3.19) generating Ninja** build files. The legacy autotools-style + build is gone; `make bootstrap` is a no-op. +- **Compilers:** Clang (preferred/default when `CC=cc`) or GCC, both **>= 9.0.0**. + The build is compiled with `-Werror` in practice, so warnings are failures. +- **External dependencies:** built into `build/external` (DPDK `26.07`, + rdma-core `64.0`, ipsec-mb, quicly, libcbor, xdp-tools, daq, octeon-roc). + `build/optional` holds optional deps. See `build/external/packages/*.mk`. +- **Docs:** Sphinx (`docs/`, `docs/Makefile`, `docs/conf.py`), reStructuredText + and Markdown, with `sphinxcontrib-spelling`. +- **Multi-architecture:** performance-critical nodes are compiled once per CPU + variant (e.g. `x86_64_v3`, `avx2`, `avx512`, `neoverse-*`), and the best + variant is selected at runtime. Platform tuning files live in + `src/cmake/platform/` (`icelake-server`, `sapphirerapids`, `emeraldrapids`, + `neoverse-n1/n2/v2`, `octeon9/10`, `cn913x`). + +--- + +## 4. Build system and commands + +All commands are run from the repository root. `make` with no target prints +the target list (`make help` is the same). + +### One-time setup + +```bash +make install-dep # install distro build dependencies (sudo) +make install-ext-deps # build/install DPDK, rdma-core, etc. into build/external +make install-opt-deps # optional dependencies (not always needed) +``` + +`make install-dep` also runs +`git config commit.template .git_commit_template.txt`. + +### Build + +```bash +make build # debug binaries -> build-root/build-vpp_debug-native +make build-release # release binaries -> build-root/build-vpp-native +make rebuild # wipe + build (debug) +make rebuild-release # wipe + build (release) +make wipe # remove debug build products +``` + +Outputs land in `build-root/`: +`build-vpp-native/` (release), `build-vpp_debug-native/` (debug), +`build-vpp_gcov-native/` (coverage), and matching `install-vpp-*` trees. + +### Incremental C compilation (much faster than a full `make build`) + +The CMake/Ninja tree is directly usable: + +```bash +ninja -C build-root/build-vpp_debug-native/vpp +# e.g. a single plugin: +ninja -C build-root/build-vpp_debug-native/vpp lib/x86_64-linux-gnu/vpp_plugins/gre_plugin.so +``` + +`make compdb` regenerates `compile_commands.json` at the repo root (used by +clangd/IDEs; gitignored). + +### Run / debug the binary + +```bash +make run # run debug vpp (needs sudo for hugepages/NIC access) +make run-release # run release vpp +make debug # run debug vpp under gdb +make debug-release # run release vpp under gdb +make run-vat # run the vpp-api-test tool +``` + +Useful make arguments: `V=1` (verbose build), `STARTUP_CONF=`, +`STARTUP_DIR=`, `GDB=`, `PLATFORM=`, `DPDK_CONFIG=<...>` +(e.g. `"no-pci"`), `SAMPLE_PLUGIN=yes`, `DISABLED_PLUGINS=`. +`src/vpp/conf/startup.conf` is the reference startup configuration +(`unix`, `api-trace`, `api-segment`, `socksvr`, `cpu`, `dpdk`, ... stanzas). + +### Alternative: the `configure` script + +`./configure` is an experimental, standalone CMake driver for out-of-tree or +cross builds (`--build-type`, `--platform`, `--arch`, `--plugins`, `--drivers`, +`--crypto-engines`, `--minimal`, `--native-only`, `--sanitize mem`, ...). +Consult its `--help` before changing it; the comment asks to check with the +maintainer first. + +### Sanitizers + +CMake option `-DVPP_ENABLE_SANITIZE_ADDR=ON` enables AddressSanitizer; the +default ASAN options are set in `src/CMakeLists.txt`. The test harness exports +`ASAN_OPTIONS` from `test/Makefile`. + +--- + +## 5. Code organization and conventions + +### Layer boundaries + +`vppinfra` → `svm` → `vlib` → `vlibmemory`/`vlibapi` → `vnet` → `vpp`. +Keep dependencies pointing downward. `src/CMakeLists.txt` lists `SUBDIRS` in +that order ("order matters"). Plugins link against the core libraries; core +code must not depend on plugins. + +### Plugin structure (the dominant unit of new code) + +A plugin directory typically contains: + +``` +CMakeLists.txt # add_vpp_plugin(...) +plugin.c # VLIB_PLUGIN_REGISTER() { .version = VPP_BUILD_VER, .description = ... } +FEATURE.yaml # machine-readable feature metadata (required, schema-validated) +.api # binary API definitions (if the plugin has an API) +_api.c # API message handlers (generated header + manual handlers) +.h # plugin-internal header +node.c # graph node dispatch functions +.c # main/init/CLI +error.def # error-string definitions, included via foreach__error +``` + +Example — `src/plugins/gre/CMakeLists.txt`: + +```cmake +add_vpp_plugin(gre + SOURCES gre.c node.c gre_api.c interface.c pg.c plugin.c + MULTIARCH_SOURCES node.c gre.c + INSTALL_HEADERS gre.h error.def + API_FILES gre.api +) +``` + +`add_vpp_plugin` (defined in `src/cmake/plugin.cmake`) supports: +`SOURCES`, `MULTIARCH_SOURCES`, `MULTIARCH_FORCE_ON`, `API_FILES`, +`API_TEST_SOURCES`, `VAT_AUTO_TEST`, `INSTALL_HEADERS`, `LINK_LIBRARIES`, +`LINK_FLAGS`, `COMPONENT`, `DEV_COMPONENT`, `SUPPORTED_OS_LIST`. +It installs the plugin to `/vpp_plugins` and any API test plugin to +`vpp_api_test_plugins` / `vat2_plugins`. + +A generator exists for new plugins: +`cd src/plugins && ../../extras/emacs/make-plugin.sh`. After adding a plugin +directory you generally just rebuild; no other file needs editing (the glob +picks it up). See `docs/developer/plugindoc/add_plugin.rst`. + +### Feature metadata (`FEATURE.yaml`) + +Every plugin/component should ship a `FEATURE.yaml` (105 exist). It is +validated against a JSON schema by `extras/scripts/fts.py`: + +- `name`, `description`, `features` (list), `state` ∈ + {production, experimental, development}, + `properties` ⊆ {API, CLI, STATS, MULTITHREAD}, `maintainer` (string or list). + +Validate with `make checkfeaturelist`; dump the list with `make featurelist`. + +### Multi-architecture nodes + +Follow `docs/developer/corearchitecture/multiarch/nodefns.rst`: + +- Declare the node function with `VLIB_NODE_FN ()`, and the function + name **must** match the graph node name. +- Bracket the single copies of `VLIB_REGISTER_NODE`, error strings, and trace + format functions in `#ifndef CLIB_MARCH_VARIANT ... #endif`. +- **Never** set the `.function` member of `VLIB_REGISTER_NODE`; the multiarch + constructor selects it at runtime. +- Mark the body inline with `always_inline` (not `static inline`), or the + compiler will not emit per-variant copies. +- List the file under `MULTIARCH_SOURCES` in `CMakeLists.txt`. + +### Binary API (`.api` files) + +The API is defined in a custom IDL and compiled by `src/tools/vppapigen`. See +`docs/interfacing/binapi/vpp_api_language.rst`. Key points: + +- `option version = "X.Y.Z";` at the top of each `.api` file. +- Request/reply naming conventions: `` / `_reply`; + dump/detail: `_dump` / `_details`; events: `want_*` + + `service { rpc ... events ... }`. +- Every request carries `u32 client_index; u32 context;`. +- `autoreply` is shorthand for a reply containing only `retval`. +- API message CRCs are part of the wire contract. `make checkstyle-api` + (`extras/scripts/crcchecker.py --check-patchset`) rejects incompatible + changes to APIs with semantic version >= 1.0.0. If an API change is + intentional, the CRC must be deliberately updated and the change discussed + (the checker's failure message points at the API change process). +- Generated JSON/header artifacts are produced by + `make json-api-files`, `make go-api-files`. + +### License headers + +New files must contain an `SPDX-License-Identifier:` line (checked by +`make checkstyle` for added `*.c`, `*.h`, `*.cmake`, `*.py`, `CMakeLists.txt`). +The tree is migrating from the long Apache-2.0 boilerplate to the short SPDX +form (thousands of files already use `/* SPDX-License-Identifier: Apache-2.0 +* Copyright (c) ... */`). New code should use the SPDX form. + +### Coding style (C) + +- `.clang-format` is authoritative: **GNU base style, tabs (`UseTab: Always`), + column limit 100, 2-space continuation indent**, `SpaceAfterCStyleCast: true`, + includes not sorted. Minimum `clang-format` version is 11. +- `.clang-tidy` enables `misc-*`, `bugprone-*` (minus swappable-params and + reserved-identifier), `performance-*`, `clang-analyzer-*` (minus + `valist.Uninitialized`). +- `make checkstyle` (via `extras/scripts/checkstyle.sh`) checks the **most + recent commit** (`CHECKSTYLE_COMMIT=HEAD~N` to override) and fails on: + trailing whitespace, Emacs indentation-control comments on modified lines, + missing SPDX in new files, the deprecated + `fd.io coding-style-patch-verification` footer, and clang-format diffs. +- `make fixstyle` applies clang-format to the current commit's diff. +- The expected clang-format major version is set per OS in the top-level + `Makefile` (`CLANG_FORMAT_VER`, e.g. 11, 15, 19). A mismatch makes + `checkstyle` fail with a version error. +- Avoid Emacs indentation-control comments in new or modified code. Note that + `checkstyle.sh` detects them by grepping added lines for the literal marker, + so even a comment *quoting* the marker trips the check. + +### Python style + +Python under `test/` (and repo-wide) is formatted with **black**, target +`py39`: `make checkstyle-python` / `make fixstyle-python`. The test harness +installs its own pinned toolchain in a virtualenv. + +### Go style + +`test/hs-test` and `extras/kube-test` are checked with `goimports` +(`v0.39.0`): `make checkstyle-go` / `make fixstyle-go`. Go modules are +`fd.io/hs-test` (go 1.26.5) and the kube-test module. + +### Commit messages (enforced) + +Format is described in `.git_commit_template.txt` and enforced by +`extras/scripts/check_commit_msg.sh` (`make checkstyle-commit`): + +- Subject: `: `, max ~50 chars, lowercase, imperative, + no trailing period. `feature-name` **must be a feature id present in + `MAINTAINERS`** (the `I:` lines). Commits spanning multiple components + should be split. +- Body explains *why*, wrapped ~72 chars, with a `Type:` line whose value is + one of: `feature`, `fix`, `refactor`, `improvement`, `style`, `docs`, + `test`, `make`, `ci`. +- Optional `Ticket:`, `Fixes:`, `Change-Id:`, `Signed-off-by:` lines. + +Example subject lines from this tree: `linux-cp: drop nexthops on deleted +interfaces`, `fib: guard attached export against bad fib index`, +`build: make deb version vary per commit`. + +### Documentation style + +Docs live as `.rst`/`.md` next to the code in `src/` (and `test/`, `extras/`) +and are **symlinked** into `docs/`. `extras/scripts/check_documentation.sh` +verifies that every doc file is either linked from `docs/` or listed in +`docs/docsignore`. Build with `make docs`, clean with `make docs-clean`, spell +check with `make docs-spell`; output is `build-root/docs/html/index.html`. + +--- + +## 6. Testing + +There are three test systems. "Over 1,000 unit test vectors" are referenced in +`docs/developer/build-run-debug/testing_vpp.rst`. + +### 6.1 Python functional tests (`test/`) — the primary harness + +Built on Python `unittest` + **Scapy** for packet construction/inspection. + +- Base classes: `test/framework.py::VppTestCase` (packet-generator/scapy tests) + and `test/asfframework.py::VppAsfTestCase` ("A Scapy Free" tests under + `test/asf/`, which use VPP's own packet generator without Scapy). +- Lifecycle: `setUpClass` → `setUp` → `test_` → `tearDown` → + `tearDownClass`. Each test class gets its own temporary directory + (`/tmp/vpp-unittest--XXXXXX`) and shared-memory prefix, so tests + are isolated; each directory holds `log.txt`, `pg*_in.pcap`, `pg*_out.pcap`. +- Object model helpers live in `test/vpp_*.py` (e.g. `vpp_gre_interface.py`, + `vpp_ip_route.py`); assertions use `unittest` assert methods plus + `self.statistics` counters. +- Dependencies are pinned: `test/requirements.txt` (source) is compiled into + `test/requirements-3.txt` (hash-pinned lockfile). The venv is created at + `build-root/test/venv`, using `uv` when available and falling back to + pip/pip-tools. `test/patches/scapy-/*.patch` are applied to Scapy + after install. +- Tests can run in parallel; each suite is a forked process. + +Commands (run from the repo root): + +```bash +make test # build vpp + run basic functional tests +make test-debug # same, against the debug image +make test-all # basic + extended tests (EXTENDED_TESTS=1) +make retest # run tests without rebuilding vpp +make TEST=test_gre test # one file +make TEST=gre test # 'test_' prefix omitted +make TEST='gre.TestGRE.test_gre' test # file.class.method, wildcards allowed +make TEST_JOBS=auto test # parallel processes +make test-list TEST=... # show which tests the filter selects, no run +make test-help # full list of test targets and variables +make test-shell # interactive shell with the test env activated +make test-start-vpp-in-gdb # start a vpp for DEBUG=attach +make test-wipe # clean temp files/venvs +``` + +Important test variables: `V=[0|1|2]` (verbosity), `TEST_JOBS=[n|auto]`, +`MAX_VPP_CPUS`, `FAILFAST`, `TIMEOUT` (default 600s), `RETRIES`, `STEP`, +`SANITY`, `EXTENDED_TESTS`, `VARIANT=` (test a specific multiarch +variant, e.g. `skx`), `RND_SEED`, `CACHE_OUTPUT`, `USE_SMT`, `SKIP_TESTS`, +`DEBUG=`. + +Failed tests are copied to `/tmp/vpp-failed-unittests/` and, in CI, archived. + +When adding a test for a feature, look for an existing `test/test_.py` +and mirror its structure; plugin tests commonly skip themselves when the +plugin is excluded (`config.excluded_plugins`). + +### 6.2 Host-stack end-to-end tests (`test/hs-test`) — Go + Ginkgo + Docker + +End-to-end tests for the host stack that need multiple VPP instances, network +namespaces, and external tools. Written in Go with Ginkgo; topology is built +with Docker and `ip`. Requires root and a sane `kernel.core_pattern` (no pipe, +otherwise core dumps are not detected). See `test/hs-test/README.rst`. + +```bash +make -C test/hs-test build # build test infra / docker images +make -C test/hs-test test # run tests +make -C test/hs-test test-debug # run against debug VPP image +make -C test/hs-test list-tests +make -C test/hs-test test TEST=MyTest +make -C test/hs-test test PARALLEL=4 PERSIST=true V=2 +make cleanup-hst # remove containers/namespaces from last run +``` + +Suites are grouped by topology and run in parallel; `RUN_ID` isolates +concurrent runs on one checkout. + +### 6.3 C unit tests + +- `src/vppinfra/test/test_*.c` are standalone C unit tests, built only when + `-DVPP_BUILD_VPPINFRA_TESTS=ON`. They are registered via `add_vpp_test` in + `src/vppinfra/CMakeLists.txt`. +- Individual components have in-tree tests, e.g. `src/vnet/interface_test.c`, + `src/vpp-api/client/test.c`, and per-plugin API test plugins + (`API_TEST_SOURCES`). +- `src/tools/vppapigen/test_vppapigen.py` tests the API generator and is run by + `make verify`. + +### 6.4 Coverage and "make verify" + +```bash +make test-cov # gcov build + tests + lcov HTML report +make test-cov-both # merge Python + Go (hs-test) coverage +make test-cov-build / test-cov-prep / test-cov-post # phased coverage +make test-cov-hs # host-stack tests with coverage +make cov-merge # merge + genhtml the lcov traces +``` + +Report output: `build-root/test-coverage-merged/html/index.html`. Details in +`docs/developer/build-run-debug/code_coverage.rst`. + +`make verify` runs `pkg-verify` (build packages + sample-plugin + libmemif), +then `vppapigen` tests and `make test` — only on Ubuntu 22.04 +(`MAKE_VERIFY_GATE_OS`). + +### 6.5 CI + +CI is GitHub Actions under `.github/workflows/`. This fork keeps exactly two +workflows, both on GitHub-hosted runners: + +- `pr-verify.yml` — runs on `pull_request` against `master`. Two jobs: + - `style` (no build): `make checkfeaturelist`, `make checkstyle` over the + whole pull request, `make checkstyle-api`, `make checkstyle-python`, and + `extras/scripts/check_commit_msg.sh` for every non-merge commit in the PR. + - `build and test`: installs dependencies, builds VPP debug with `-Werror` + under ccache, derives a focused `TEST=` filter from the changed paths + (falling back to the `ip4` smoke suite), and runs `make test-debug`. +- `build-deb-trixie.yml` — builds Debian 13 (Trixie) DEB packages on push to + `master` (and can publish a GitHub Release). + +Both jobs run inside a `debian:trixie` container, which matters for the shell: +a container job gets `sh` (dash in this image) for `run:` steps unless the job +sets `defaults: run: shell: bash`, and dash rejects the bash parameter +expansions used by the test-filter step. + +The upstream FD.io workflows are **not** present here. They targeted FD.io's +self-hosted Nomad runners and consumed `fdio/*` actions plus AWS secrets, none +of which exist on this fork, so they sat queued forever; they were deleted. +Do not reintroduce a `self-hosted` runner target, an `fdio/` action, or an AWS +secret into a workflow. + +The test filter only ever emits a name for which `test/test_.py` exists +exactly, because the runner resolves a bare filter to that file name and +matches it literally — a name with no module selects nothing and fails the job +instead of falling back. For `src/plugins///...` the subdirectory is +tried first, so `src/plugins/nat/nat64/nat64_db.c` selects `nat64`. + +Reusable composite actions still live in `.github/actions/` (`vpp-build`, +`vpp-make-test`, `vpp-install-deps`, `vpp-install-ext-deps`, +`vpp-docker-runtime-setup`), but the two workflows above do not currently use +them. + +Dependabot is configured for `github-actions` only, with PRs disabled +(`open-pull-requests-limit: 0`). + +Note that `lsp_diagnostics` and similar tools that resolve paths against their +own working directory cannot see this repository, so compilation and formatting +are verified by CI and by `extras/scripts/checkstyle.sh` run locally with the +pinned `CLANG_FORMAT_VER=19`, which is hunk-scoped. + +Run the local equivalents of the CI gates before considering a change done: + +```bash +make checkstyle # C formatting + feature list +make checkstyle-commit # commit message (feature id + Type:) +make checkstyle-python # black +make checkstyle-api # API CRC compatibility +make checkstyle-go # goimports +make test-checkstyle # Python + Go test-code style +make docs-spell # Sphinx spelling +``` + +--- + +## 7. Security considerations + +The project has an explicit security policy in `SECURITY.md`; the summary below +is not a substitute for reading it. + +- **Report vulnerabilities privately** to `security@lists.fd.io`. Do not open a + public issue for a security bug. The full process is in + `docs/aboutvpp/security/tsc_vulnerability_management_v2.rst`; past advisories + are in `docs/aboutvpp/security/security_advisories.rst`. +- **What counts as a security bug:** a bug exploitable through the + **dataplane** — i.e. triggered by crafted packets arriving on an interface + and processed by the forwarding graph, with no prior privileged access. + Examples: a crafted packet that crashes VPP (DoS) or achieves RCE; any + memory-safety flaw reachable from normal packet processing. +- **What does NOT count:** bugs reachable only through the **binary API** or + the debug CLI (`vppctl`). Those interfaces are *trusted* — the caller is + assumed to be in the same administrative trust domain as VPP. Such bugs + should still be fixed, but through the normal public bug process. +- Concretely for agents: when auditing or modifying dataplane code, treat + packet-derived lengths/indices/offsets as untrusted and validate them. When a + crash requires an API/CLI precondition, it is a regular bug, not a CVE. +- The debug CLI (`vppctl`) and the API socket are privileged interfaces: + `unix { cli-listen /run/vpp/cli.sock ... }` in the startup config controls + access, and the `api-segment`/`unix` stanzas set the `gid` used to restrict + socket permissions. +- VPP runs as a privileged process using hugepages and (for DPDK) kernel + drivers such as `uio_pci_generic`; see `docs/gettingstarted/running/`. + +--- + +## 8. Packaging and deployment + +```bash +make pkg-deb # build DEB packages +make pkg-deb-debug # debug DEB packages +make pkg-rpm # build RPM packages (depends on `make dist`) +make pkg-srpm # source RPM +make pkg-snap # build SNAP package +make pkg-verify # full package build: vpp + sample-plugin + libmemif + pkg-$(PKG) +``` + +- Package flavor is auto-detected from `/etc/os-release`: `deb` (Ubuntu, Debian, + LinuxMint), `rpm` (RHEL, CentOS, Fedora, openSUSE-Leap, Rocky, AlmaLinux, + Anolis, Kylin), `pkg` (FreeBSD). +- `make dist` produces `build-root/vpp-.tar.xz`. +- Debian packaging metadata is under `build-root/deb/debian/` and + `extras/deb/`; RPM under `extras/rpm/`; snap under `extras/snap/`. +- On install, VPP creates a `vpp` user/group, a systemd unit + (`vpp.service`), hugepage sysctls (`/etc/sysctl.d/80-vpp.conf`), and a + default `/etc/vpp/startup.conf`. Add users who need `vppctl` to the `vpp` + group (`usermod -a -G vpp `). See `docs/gettingstarted/running/`. +- `extras/vagrant/build.sh` bootstraps dependencies, builds and installs VPP on + a Linux host; `extras/vagrant/` has a Vagrant VM for development. +- `extras/vpp_config/` is a Python tool for generating `startup.conf` for a + given set of NICs; `extras/scripts/` contains operational helpers + (`pci-nic-bind`, `lsnet`, `vfctl`, `vpp-review`, ...). + +Version numbering: `src/scripts/version` derives the version from +`git describe --long --match "v*"`, with fallbacks for repos without tags. In +this fork the deb workflow synthesises versions of the form +`0.0.0-release+~g` so that rebuilt packages sort strictly above +earlier builds (see commits `7ee6487a3` and `bc8310875`); this matters because +an unchanged version string makes apt skip the new package and can leave +mismatched shared libraries installed. + +--- + +## 9. Fork-specific context (important for this checkout) + +The commits on `master` beyond `upstream/master` are largely **defensive +hardening and crash fixes** to dataplane and control-plane paths, plus build/CI +work. Areas touched: `linux-cp` (netlink error recovery, interface liveness +validation), `fib` (bad index guards, attached-export), `gre`, `teib`, +`vlib` buffer/handoff cleanup, `svm` mapping/fifo rollback, `session` fifo +migration, `rdma`, `cnat`, `nat`, `memif`, `flowprobe`, `dpdk` (incl. Huawei +Hi1822/hinic PMD handling), `vhost`, `tls`, `timer`/`tw_timer`, `vapi` message +size bounds, `http`/`http_static` parsing, `iavf` TX placeholders, `sfdp`, and +`.github/workflows` (the unrunnable FD.io workflows removed, a GitHub-hosted +pull-request verification workflow added, Debian 13 DEB build kept). + +Practical implications: + +- Fixes here often validate indices/handles derived from external input + (netlink, API, packets) before use. Match that style: prefer explicit + validation and a graceful skip/drop over dereferencing. +- The tree is kept mergeable with upstream. Prefer minimal, upstreamable + changes; avoid introducing fork-only abstractions into core code. +- Commit subjects still use upstream feature ids from `MAINTAINERS`, and + `Type: fix` is used on bug fixes. +- `.omo/` and `.sisyphus/` are **local agent workspace metadata** + (`run-continuation/`, handoff notes, patches). They are gitignored + (`.gitignore`: "Agent workspace metadata") and are not part of the product — + do not treat their contents as project documentation or commit them. +- `.codegraph` is a symlink to a local code-index cache; also not part of the + project. + +--- + +## 10. Quick reference + +```bash +# Build +make install-dep && make install-ext-deps +make build # debug +make build-release # release +ninja -C build-root/build-vpp_debug-native/vpp # incremental + +# Run +make run # debug binary +make debug # debug under gdb + +# Test +make test # python functional tests +make TEST=gre test # filtered +make TEST_JOBS=auto test # parallel +make test-all # + extended +make -C test/hs-test test # host-stack e2e (Go/Ginkgo) +make test-cov # coverage report +make test-help # all options + +# Style / gates +make checkstyle # clang-format on last commit +make fixstyle # auto-format last commit +make checkstyle-commit # commit message +make checkstyle-python # black +make checkstyle-api # API CRC compatibility +make checkstyle-go # goimports +make checkfeaturelist # FEATURE.yaml schema + +# Docs +make docs # -> build-root/docs/html/index.html +make docs-clean +``` + +Ground rules for agents working in this tree: + +1. Read `MAINTAINERS` for the owning maintainer and the correct feature id + before writing a commit message. +2. Keep changes scoped to one component per commit (`feature-name:` prefix). +3. New C files get an `SPDX-License-Identifier` header; formatting is + clang-format with tabs and a 100-column limit. +4. Treat packet-derived data as untrusted; validate indices, lengths, and + handles before use. +5. Add or extend tests under `test/` (Python) or `test/hs-test` (Go) when + behavior changes, and run the relevant `make test TEST=...` and style + gates before finishing. +6. Update `FEATURE.yaml` and the `.api` version/CRC when a plugin's feature set + or API changes. diff --git a/extras/scripts/host-stack/perf-tests/utils.sh b/extras/scripts/host-stack/perf-tests/utils.sh index f8654ad0b1ca..c46614b254d2 100644 --- a/extras/scripts/host-stack/perf-tests/utils.sh +++ b/extras/scripts/host-stack/perf-tests/utils.sh @@ -119,4 +119,3 @@ function adjust_cpus_for_numa() { echo "Updated MAIN_CORE to: $MAIN_CORE" echo "Updated CFG_CORELIST_WKS to: $CFG_CORELIST_WKS" } - diff --git a/src/drivers/iavf/queue.c b/src/drivers/iavf/queue.c index a48c33b45652..46f1f3b33477 100644 --- a/src/drivers/iavf/queue.c +++ b/src/drivers/iavf/queue.c @@ -60,6 +60,9 @@ iavf_tx_queue_alloc (vlib_main_t *vm, vnet_dev_tx_queue_t *txq) vnet_dev_t *dev = txq->port->dev; iavf_device_t *ad = vnet_dev_get_data (dev); iavf_txq_t *atq = vnet_dev_get_tx_queue_data (txq); + u8 bpi = vlib_buffer_pool_get_default_for_numa (vm, dev->numa_node); + u16 n_ph_bufs = (txq->size / 510) + 1; + u32 n_alloc; vnet_dev_rv_t rv; if ((rv = @@ -68,6 +71,22 @@ iavf_tx_queue_alloc (vlib_main_t *vm, vnet_dev_tx_queue_t *txq) return rv; clib_ring_new_aligned (atq->rs_slots, 32, CLIB_CACHE_LINE_BYTES); + atq->ph_bufs = + clib_mem_alloc_aligned (n_ph_bufs * sizeof (atq->ph_bufs[0]), CLIB_CACHE_LINE_BYTES); + if (atq->ph_bufs == 0) + { + rv = VNET_DEV_ERR_BUFFER_ALLOC_FAIL; + goto error; + } + n_alloc = vlib_buffer_alloc_from_pool (vm, atq->ph_bufs, n_ph_bufs, bpi); + if (n_alloc != n_ph_bufs) + { + if (n_alloc) + vlib_buffer_free (vm, atq->ph_bufs, n_alloc); + rv = VNET_DEV_ERR_BUFFER_ALLOC_FAIL; + goto error; + } + atq->buffer_indices = clib_mem_alloc_aligned ( txq->size * sizeof (atq->buffer_indices[0]), CLIB_CACHE_LINE_BYTES); atq->tmp_descs = clib_mem_alloc_aligned ( @@ -79,6 +98,16 @@ iavf_tx_queue_alloc (vlib_main_t *vm, vnet_dev_tx_queue_t *txq) log_debug (dev, "queue %u alocated", txq->queue_id); return VNET_DEV_OK; + +error: + if (atq->ph_bufs) + { + clib_mem_free (atq->ph_bufs); + atq->ph_bufs = 0; + } + clib_ring_free (atq->rs_slots); + vnet_dev_dma_mem_free (vm, dev, atq->descs); + return rv; } void @@ -87,12 +116,16 @@ iavf_tx_queue_free (vlib_main_t *vm, vnet_dev_tx_queue_t *txq) vnet_dev_t *dev = txq->port->dev; iavf_txq_t *atq = vnet_dev_get_tx_queue_data (txq); iavf_txq_t *aq = vnet_dev_get_tx_queue_data (txq); + u16 n_ph_bufs = (txq->size / 510) + 1; log_debug (dev, "queue %u", txq->queue_id); vnet_dev_dma_mem_free (vm, dev, aq->descs); clib_ring_free (atq->rs_slots); - foreach_pointer (p, aq->tmp_descs, aq->tmp_bufs, aq->buffer_indices) + if (atq->ph_bufs) + vlib_buffer_free (vm, atq->ph_bufs, n_ph_bufs); + + foreach_pointer (p, aq->ph_bufs, aq->tmp_descs, aq->tmp_bufs, aq->buffer_indices) if (p) clib_mem_free (p); } diff --git a/src/plugins/cnat/cnat_api.c b/src/plugins/cnat/cnat_api.c index 7806e9de0f97..f853185240db 100644 --- a/src/plugins/cnat/cnat_api.c +++ b/src/plugins/cnat/cnat_api.c @@ -156,7 +156,10 @@ vl_api_cnat_translation_update_t_handler (vl_api_cnat_translation_update_t goto done; n_paths = clib_net_to_host_u32 (mp->translation.n_paths); - vec_validate (paths, n_paths - 1); + /* An empty path list is a valid message: n_paths - 1 would wrap to + * ~0 and request an absurd allocation. */ + if (n_paths) + vec_validate (paths, n_paths - 1); for (pi = 0; pi < n_paths; pi++) { diff --git a/src/plugins/cnat/cnat_node.h b/src/plugins/cnat/cnat_node.h index ae12dc0e23b6..fca92ea6a34b 100644 --- a/src/plugins/cnat/cnat_node.h +++ b/src/plugins/cnat/cnat_node.h @@ -926,6 +926,7 @@ cnat_rsession_create (cnat_timestamp_rewrite_t *rw, u32 flow_id, u32 ret_fib_ind { cnat_bihash_kv_t rkey = { 0 }; cnat_session_t *rsession = (cnat_session_t *) &rkey; + cnat_session_stale_cleanup_t cleanup = { 0 }; /* For ICMP echo, the echo identifier is a single field mapped to both * ports in the 5-tuple. Sync port[VLIB_TX] with port[VLIB_RX] (which @@ -993,9 +994,11 @@ cnat_rsession_create (cnat_timestamp_rewrite_t *rw, u32 flow_id, u32 ret_fib_ind (*sport_failures)++; } - cnat_bihash_add_with_overwrite_cb (&cnat_session_db, &rkey, cnat_session_free_stale_cb, NULL); + cnat_bihash_add_with_overwrite_cb (&cnat_session_db, &rkey, cnat_session_free_stale_cb, + &cleanup); out: + cnat_session_cleanup_stale (&cleanup); cnat_log_session_create (rsession); } diff --git a/src/plugins/cnat/cnat_node_lookup.c b/src/plugins/cnat/cnat_node_lookup.c index 5447cd2b0c01..635ba7bee4a2 100644 --- a/src/plugins/cnat/cnat_node_lookup.c +++ b/src/plugins/cnat/cnat_node_lookup.c @@ -88,6 +88,7 @@ cnat_writeback_new_flow (vlib_buffer_t *b, ip_address_family_t af, u16 *next) cnat_bihash_kv_t bkey; cnat_timestamp_t *ts; cnat_session_t *session = (cnat_session_t *) &bkey; + cnat_session_stale_cleanup_t cleanup = { 0 }; u32 n_retries = 0, rv, port_seed = 0; cnat_main_t *cm = &cnat_main; @@ -130,9 +131,11 @@ cnat_writeback_new_flow (vlib_buffer_t *b, ip_address_family_t af, u16 *next) vnet_buffer2 (b)->session.state = CNAT_LOOKUP_IS_DONE; retry_add_session: + cleanup.found = false; rv = cnat_bihash_add_with_overwrite_cb ( &cnat_session_db, &bkey, - n_retries < cm->session_max_port_retries ? NULL : cnat_session_free_stale_cb, NULL); + n_retries < cm->session_max_port_retries ? NULL : cnat_session_free_stale_cb, &cleanup); + cnat_session_cleanup_stale (&cleanup); if (rv && n_retries++ < cm->session_max_port_retries) { random_u32 (&port_seed); diff --git a/src/plugins/cnat/cnat_session.c b/src/plugins/cnat/cnat_session.c index 87c483f19a55..8cc8c9d948f6 100644 --- a/src/plugins/cnat/cnat_session.c +++ b/src/plugins/cnat/cnat_session.c @@ -33,19 +33,17 @@ cnat_ep_trk_delete_notify (index_t *trk_index) void (*cnat_free_port_cb) (u32 fib_index, u16 port, ip_protocol_t iproto); -typedef struct cnat_session_walk_ctx_t_ +typedef struct cnat_session_snapshot_ctx_t_ { - cnat_session_walk_cb_t cb; - void *ctx; -} cnat_session_walk_ctx_t; + cnat_bihash_kv_t *sessions; +} cnat_session_snapshot_ctx_t; static int -cnat_session_walk_cb (BVT (clib_bihash_kv) * kv, void *arg) +cnat_session_snapshot_cb (BVT (clib_bihash_kv) * kv, void *arg) { - cnat_session_t *session = (cnat_session_t *) kv; - cnat_session_walk_ctx_t *ctx = arg; + cnat_session_snapshot_ctx_t *ctx = arg; - ctx->cb (session, ctx->ctx); + vec_add1 (ctx->sessions, *kv); return (BIHASH_WALK_CONTINUE); } @@ -53,27 +51,20 @@ cnat_session_walk_cb (BVT (clib_bihash_kv) * kv, void *arg) void cnat_session_walk (cnat_session_walk_cb_t cb, void *ctx) { - cnat_session_walk_ctx_t wctx = { - .cb = cb, - .ctx = ctx, - }; - BV (clib_bihash_foreach_key_value_pair) (&cnat_session_db, - cnat_session_walk_cb, &wctx); -} - -typedef struct cnat_session_purge_walk_t_ -{ - cnat_bihash_kv_t *keys; -} cnat_session_purge_walk_ctx_t; + cnat_session_snapshot_ctx_t snapshot = { 0 }; + cnat_bihash_kv_t *kv; + vlib_main_t *vm = vlib_get_main (); -static int -cnat_session_purge_walk (BVT (clib_bihash_kv) * key, void *arg) -{ - cnat_session_purge_walk_ctx_t *ctx = arg; + vlib_worker_thread_barrier_sync (vm); + BV (clib_bihash_foreach_key_value_pair) (&cnat_session_db, + cnat_session_snapshot_cb, &snapshot); + vlib_worker_thread_barrier_release (vm); - vec_add1 (ctx->keys, *key); + vec_foreach (kv, snapshot.sessions) + if (WALK_STOP == cb ((cnat_session_t *) kv, ctx)) + break; - return (BIHASH_WALK_CONTINUE); + vec_free (snapshot.sessions); } u8 * @@ -124,15 +115,12 @@ format_cnat_timestamp (u8 *s, va_list *args) return (s); } -u8 * -format_cnat_session (u8 * s, va_list * args) +static u8 * +format_cnat_session_data (u8 *s, const cnat_session_t *sess, const cnat_timestamp_t *ts, + int verbose) { - cnat_session_t *sess = va_arg (*args, cnat_session_t *); - int verbose = va_arg (*args, int); u32 indent = format_get_indent (s); - cnat_timestamp_t *ts = NULL; - ts = cnat_timestamp_get (sess->value.cs_session_index); s = format (s, "%U => [%U]\n%Uindex:%d fib:%d\n%U%U", format_cnat_5tuple, &sess->key.cs_5tuple, format_cnat_session_flags, sess->value.cs_flags, format_white_space, indent + 2, sess->value.cs_session_index, sess->key.fib_index, format_white_space, indent + 2, @@ -141,6 +129,16 @@ format_cnat_session (u8 * s, va_list * args) return (s); } +u8 * +format_cnat_session (u8 * s, va_list * args) +{ + cnat_session_t *sess = va_arg (*args, cnat_session_t *); + int verbose = va_arg (*args, int); + cnat_timestamp_t *ts = cnat_timestamp_get (sess->value.cs_session_index); + + return format_cnat_session_data (s, sess, ts, verbose); +} + static int cnat_show_yield (vlib_main_t *vm, f64 *start) { @@ -157,6 +155,17 @@ cnat_show_yield (vlib_main_t *vm, f64 *start) return 0; } +typedef struct +{ + cnat_session_t session; + cnat_timestamp_t timestamp; +} cnat_session_show_snapshot_t; + +typedef struct +{ + cnat_session_show_snapshot_t *sessions; +} cnat_session_show_snapshot_ctx_t; + typedef struct { vlib_main_t *vm; @@ -172,9 +181,31 @@ typedef struct } cnat_session_show_cbak_arg_t; static int -cnat_session_show_cbak (BVT (clib_bihash_kv) * kvp, void *arg) +cnat_session_show_snapshot_cb (BVT (clib_bihash_kv) * kvp, void *arg) +{ + cnat_session_show_snapshot_ctx_t *ctx = arg; + cnat_session_show_snapshot_t snapshot; + + snapshot.session = *(cnat_session_t *) kvp; + snapshot.timestamp = *cnat_timestamp_get (snapshot.session.value.cs_session_index); + vec_add1 (ctx->sessions, snapshot); + + return BIHASH_WALK_CONTINUE; +} + +static u8 * +format_cnat_session_show_snapshot (u8 *s, va_list *args) +{ + cnat_session_show_snapshot_t *snapshot = va_arg (*args, cnat_session_show_snapshot_t *); + int verbose = va_arg (*args, int); + + return format_cnat_session_data (s, &snapshot->session, &snapshot->timestamp, verbose); +} + +static int +cnat_session_show_snapshot_entry (cnat_session_show_snapshot_t *snapshot, void *arg) { - const cnat_session_t *s = (void *) kvp; + const cnat_session_t *s = &snapshot->session; cnat_session_show_cbak_arg_t *a = arg; cnat_show_yield (a->vm, &a->start); @@ -199,12 +230,11 @@ cnat_session_show_cbak (BVT (clib_bihash_kv) * kvp, void *arg) if (a->refcount) { - const cnat_timestamp_t *ts = cnat_timestamp_get (s->value.cs_session_index); - if (a->refcount != ts->ts_session_refcnt) + if (a->refcount != snapshot->timestamp.ts_session_refcnt) return BIHASH_WALK_CONTINUE; } - vlib_cli_output (a->vm, "%U\n", format_cnat_session, s, a->verbose); + vlib_cli_output (a->vm, "%U\n", format_cnat_session_show_snapshot, snapshot, a->verbose); if (a->max-- <= 0) { @@ -221,6 +251,8 @@ cnat_session_show (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { cnat_session_show_cbak_arg_t arg = {}; + cnat_session_show_snapshot_ctx_t snapshot = { 0 }; + cnat_session_show_snapshot_t *session; int v; arg.vm = vm; @@ -257,11 +289,18 @@ cnat_session_show (vlib_main_t * vm, } } + vlib_worker_thread_barrier_sync (vm); vlib_cli_output (vm, "CNat Sessions: now:%f\n%U\n", vlib_time_now (vm), BV (format_bihash), &cnat_session_db, 0 /* verbose */); if (arg.verbose) - BV (clib_bihash_foreach_key_value_pair) - (&cnat_session_db, cnat_session_show_cbak, &arg); + BV (clib_bihash_foreach_key_value_pair) (&cnat_session_db, + cnat_session_show_snapshot_cb, &snapshot); + vlib_worker_thread_barrier_release (vm); + + vec_foreach (session, snapshot.sessions) + if (BIHASH_WALK_STOP == cnat_session_show_snapshot_entry (session, &arg)) + break; + vec_free (snapshot.sessions); return (NULL); } @@ -305,32 +344,56 @@ cnat_session_free__ (cnat_session_t *session) void cnat_session_free_stale_cb (cnat_bihash_kv_t *kv, void *opaque) { - cnat_session_t *session = (cnat_session_t *) kv; + cnat_session_stale_cleanup_t *cleanup = opaque; + + cleanup->session = *kv; + cleanup->found = true; +} + +void +cnat_session_cleanup_stale (cnat_session_stale_cleanup_t *cleanup) +{ + if (!cleanup->found) + return; + + cnat_session_t *session = (cnat_session_t *) &cleanup->session; cnat_log_session_overwrite (session); cnat_session_free__ (session); + cleanup->found = false; } void cnat_session_free (cnat_session_t * session) { - cnat_bihash_kv_t *bkey = (cnat_bihash_kv_t *) session; - cnat_session_free__ (session); - cnat_bihash_add_del (&cnat_session_db, bkey, 0 /* is_add */); + cnat_bihash_kv_t current; + cnat_session_t copy = *session; + + if (cnat_bihash_search_i2 (&cnat_session_db, (cnat_bihash_kv_t *) ©, ¤t) || + memcmp (©, ¤t, sizeof (copy))) + return; + + if (cnat_bihash_add_del (&cnat_session_db, (cnat_bihash_kv_t *) ©, 0 /* is_add */)) + return; + + cnat_session_free__ (©); } int cnat_session_purge (void) { /* flush all the session from the DB */ - cnat_session_purge_walk_ctx_t ctx = { }; + cnat_session_snapshot_ctx_t snapshot = { 0 }; cnat_bihash_kv_t *key; + vlib_main_t *vm = vlib_get_main (); + vlib_worker_thread_barrier_sync (vm); BV (clib_bihash_foreach_key_value_pair) (&cnat_session_db, - cnat_session_purge_walk, &ctx); + cnat_session_snapshot_cb, &snapshot); - vec_foreach (key, ctx.keys) cnat_session_free ((cnat_session_t *) key); + vec_foreach (key, snapshot.sessions) cnat_session_free ((cnat_session_t *) key); + vlib_worker_thread_barrier_release (vm); - vec_free (ctx.keys); + vec_free (snapshot.sessions); return (0); } @@ -436,6 +499,14 @@ cnat_session_is_stale (cnat_session_t *session, f64 start_time) u64 cnat_session_scan (vlib_main_t *vm, f64 start_time, int i) { + cnat_bihash_kv_t *stale = NULL, *key; + BVT (clib_bihash) * h = &cnat_session_db; + f64 budget_start; + int j, k; + + cnat_log_scanner_start (i); + vlib_worker_thread_barrier_sync (vm); + if (!i) { /* delete confirmed deleted backends from previous scan @@ -455,19 +526,25 @@ cnat_session_scan (vlib_main_t *vm, f64 start_time, int i) */ ep_trk_del_state ^= 1; } - BVT (clib_bihash) * h = &cnat_session_db; - int j, k; - - cnat_log_scanner_start (i); if (!h->instantiated) - goto out; + goto remove_stale; + + /* + * The budget below is how long this scan may keep the workers off between + * two pauses, so it has to start here rather than at the caller's start + * time, which also counts the wait for the barrier. On a busy system that + * wait exceeds the budget on its own, the loop would then leave before its + * first bucket, hand the same starting point back to the caller and never + * examine a session. + */ + budget_start = vlib_time_now (vm); for ( /* caller saves starting point */ ; i < h->nbuckets; i++) { /* allow no more than 100us without a pause */ - if ((vlib_time_now (vm) - start_time) > 10e-5) - goto out; + if ((vlib_time_now (vm) - budget_start) > 10e-5) + goto remove_stale; if (i < (h->nbuckets - 3)) { @@ -496,29 +573,27 @@ cnat_session_scan (vlib_main_t *vm, f64 start_time, int i) cnat_session_t *session = (cnat_session_t *) & v->kvp[k]; if (cnat_session_is_stale (session, start_time)) - { - /* age it */ - cnat_log_session_expire (session); - cnat_reverse_session_free (session); - /* this should be last as deleting the session memset it to - * 0xff */ - cnat_session_free (session); - - /* - * Note: we may have just freed the bucket's backing - * storage, so check right here... - */ - if (BV (clib_bihash_bucket_is_empty) (b)) - goto doublebreak; - } + vec_add1 (stale, v->kvp[k]); } v++; } - doublebreak: - ; } -out: +remove_stale: + vec_foreach (key, stale) + { + cnat_bihash_kv_t current; + if (cnat_bihash_search_i2 (&cnat_session_db, key, ¤t) || + memcmp (key, ¤t, sizeof (*key))) + continue; + + cnat_session_t *session = (cnat_session_t *) ¤t; + cnat_log_session_expire (session); + cnat_reverse_session_free (session); + cnat_session_free (session); + } + vec_free (stale); + vlib_worker_thread_barrier_release (vm); cnat_log_scanner_stop (i); /* if at the end, return 0 to start again */ return i < h->nbuckets ? i : 0; diff --git a/src/plugins/cnat/cnat_session.h b/src/plugins/cnat/cnat_session.h index 5bbda9f2c2f7..592e506c8744 100644 --- a/src/plugins/cnat/cnat_session.h +++ b/src/plugins/cnat/cnat_session.h @@ -122,6 +122,12 @@ STATIC_ASSERT (sizeof (cnat_session_t) == sizeof (cnat_bihash_kv_t), */ extern cnat_bihash_t cnat_session_db; +typedef struct cnat_session_stale_cleanup_t_ +{ + cnat_bihash_kv_t session; + bool found; +} cnat_session_stale_cleanup_t; + /** * Callback function invoked during a walk of all sessions */ @@ -158,6 +164,11 @@ extern void cnat_session_free (cnat_session_t * session); */ extern void cnat_session_free_stale_cb (cnat_bihash_kv_t *kv, void *opaque); +/** + * Release resources recorded by cnat_session_free_stale_cb + */ +extern void cnat_session_cleanup_stale (cnat_session_stale_cleanup_t *cleanup); + /** * Port cleanup callback */ diff --git a/src/plugins/cnat/cnat_snat_policy.c b/src/plugins/cnat/cnat_snat_policy.c index 42949f2678b8..ae7bdf09c808 100644 --- a/src/plugins/cnat/cnat_snat_policy.c +++ b/src/plugins/cnat/cnat_snat_policy.c @@ -524,6 +524,48 @@ cnat_if_addr_add_del_snat_cb (addr_resolution_t *ar, ip_address_t *address, u8 i } } +/* + * The lookup of an index answers with the default policy when the index names + * no policy of its own, and the update path then records the policy it found + * under that index, so the slots of several indexes can name one entry. + * Freeing the entry has to empty all of them, and the default pointer too, or + * a later lookup follows a freed pool entry. + */ +static void +cnat_snat_policy_unregister (cnat_snat_policy_entry_t *cpe) +{ + cnat_snat_policy_main_t *cpm = &cnat_snat_policy_main; + u32 *slots[2] = { cpm->snat_policy_per_fwd_fib_index4, cpm->snat_policy_per_fwd_fib_index6 }; + u32 index = cpe - cpm->snat_policies_pool; + + for (int v = 0; v < ARRAY_LEN (slots); v++) + for (u32 i = 0; i < vec_len (slots[v]); i++) + if (index == vec_elt (slots[v], i)) + vec_elt (slots[v], i) = INDEX_INVALID; + + if (cpm->snat_default_policy == cpe) + cpm->snat_default_policy = 0; +} + +/* + * Whether the fib index names this entry, that is whether one of the slots of + * that index holds the pool index of the entry. Because of the fallback + * above, what a lookup answers with is not necessarily what the index names. + */ +static u8 +cnat_snat_policy_fib_names_entry (u32 index, u32 fwd_fib_index) +{ + cnat_snat_policy_main_t *cpm = &cnat_snat_policy_main; + + if (fwd_fib_index < vec_len (cpm->snat_policy_per_fwd_fib_index4) && + index == vec_elt (cpm->snat_policy_per_fwd_fib_index4, fwd_fib_index)) + return 1; + if (fwd_fib_index < vec_len (cpm->snat_policy_per_fwd_fib_index6) && + index == vec_elt (cpm->snat_policy_per_fwd_fib_index6, fwd_fib_index)) + return 1; + return 0; +} + static void cnat_snat_cleanup (cnat_snat_policy_main_t *cpm, cnat_snat_policy_entry_t *cpe, u32 fwd_fib_index) { @@ -539,10 +581,7 @@ cnat_snat_cleanup (cnat_snat_policy_main_t *cpm, cnat_snat_policy_entry_t *cpe, 0 /* is_session */); } - if (fwd_fib_index < vec_len (cpm->snat_policy_per_fwd_fib_index4)) - vec_elt (cpm->snat_policy_per_fwd_fib_index4, fwd_fib_index) = ~0; - if (fwd_fib_index < vec_len (cpm->snat_policy_per_fwd_fib_index6)) - vec_elt (cpm->snat_policy_per_fwd_fib_index6, fwd_fib_index) = ~0; + cnat_snat_policy_unregister (cpe); cnat_snat_policy_entry_cleanup (cpe); cnat_free_port_allocator (fwd_fib_index); @@ -550,6 +589,28 @@ cnat_snat_cleanup (cnat_snat_policy_main_t *cpm, cnat_snat_policy_entry_t *cpe, pool_put_index (cpm->snat_policies_pool, index); } +/* + * A fib index only means something if the FIB of that address family is + * there, which is the same condition fib_table_get_or_null() checks. Both + * indices of a policy are used as vector indices and, for the return + * direction, as an index into the FIB pool. + */ +static u8 +cnat_snat_fib_index_is_valid (u32 fib_index, fib_protocol_t proto) +{ + switch (proto) + { + case FIB_PROTOCOL_IP4: + return (fib_index < vec_len (ip4_main.fibs) && + !pool_is_free_index (ip4_main.fibs, fib_index)); + case FIB_PROTOCOL_IP6: + return (fib_index < vec_len (ip6_main.fibs) && + !pool_is_free_index (ip6_main.fibs, fib_index)); + default: + return 0; + } +} + __clib_export int cnat_set_snat (u32 fwd_fib_index, u32 ret_fib_index, const ip4_address_t *ip4, u8 ip4_pfx_len, const ip6_address_t *ip6, u8 ip6_pfx_len, u32 sw_if_index, @@ -567,6 +628,20 @@ cnat_set_snat (u32 fwd_fib_index, u32 ret_fib_index, const ip4_address_t *ip4, u if ((ip4_set && ip4_pfx_len > 32) || (ip6_set && (ip6_pfx_len < 64 || ip6_pfx_len > 128))) return VNET_API_ERROR_INVALID_VALUE; + /* + * The policy is kept in vectors indexed by the fwd fib index and the return + * path is installed in the ret fib index, both of which the caller (API or + * CLI) picked. Without this check an index such as ~0 makes the + * vec_validate() calls below reserve room for 2^32 entries. + */ + if ((sw_if_set || ip4_set) && (!cnat_snat_fib_index_is_valid (fwd_fib_index, FIB_PROTOCOL_IP4) || + !cnat_snat_fib_index_is_valid (ret_fib_index, FIB_PROTOCOL_IP4))) + return VNET_API_ERROR_NO_SUCH_FIB; + + if ((sw_if_set || ip6_set) && (!cnat_snat_fib_index_is_valid (fwd_fib_index, FIB_PROTOCOL_IP6) || + !cnat_snat_fib_index_is_valid (ret_fib_index, FIB_PROTOCOL_IP6))) + return VNET_API_ERROR_NO_SUCH_FIB; + cnat_lazy_init (); /* we can either: @@ -582,6 +657,13 @@ cnat_set_snat (u32 fwd_fib_index, u32 ret_fib_index, const ip4_address_t *ip4, u { /* entry found */ index = cpe - cpm->snat_policies_pool; + /* + * The lookup answers with the default policy when the fib index names + * no policy of its own; only the policy an index names may be deleted, + * and a refused delete has to leave the policy untouched. + */ + if (is_delete && !cnat_snat_policy_fib_names_entry (index, fwd_fib_index)) + return VNET_API_ERROR_NO_SUCH_FIB; /* if the interface is changed, unwatch it (note: also works for delete) */ if (cpe->snat_ip4.ce_sw_if_index != sw_if_index) @@ -723,7 +805,10 @@ cnat_set_snat_cli (vlib_main_t *vm, unformat_input_t *input, CNAT_SNAT_POLICY_FLAG_USE_AS_DEFAULT); if (rv) { - e = clib_error_return (0, "unknown error %d", rv); + if (VNET_API_ERROR_NO_SUCH_FIB == rv) + e = clib_error_return (0, "no such fib %d or rfib %d", fwd_fib_index, ret_fib_index); + else + e = clib_error_return (0, "unknown error %d", rv); goto done; } diff --git a/src/plugins/cnat/cnat_src_policy.c b/src/plugins/cnat/cnat_src_policy.c index c3d9cf266f7a..b83ad17f3383 100644 --- a/src/plugins/cnat/cnat_src_policy.c +++ b/src/plugins/cnat/cnat_src_policy.c @@ -120,7 +120,12 @@ void cnat_free_port_allocator (u32 fib_index) { cnat_src_policy_main_t *cspm = &cnat_src_policy_main; - cnat_src_port_allocator_t *src_ports = vec_elt (cspm->src_ports, fib_index); + cnat_src_port_allocator_t *src_ports; + + if (fib_index >= vec_len (cspm->src_ports)) + return; /* nothing was allocated for this fib */ + + src_ports = vec_elt (cspm->src_ports, fib_index); for (int i = 0; i < CNAT_N_SPORT_PROTO; i++) { clib_bitmap_free (src_ports[i].bmap); diff --git a/src/plugins/crypto_sw_scheduler/main.c b/src/plugins/crypto_sw_scheduler/main.c index da112b3df994..dc4205a279ba 100644 --- a/src/plugins/crypto_sw_scheduler/main.c +++ b/src/plugins/crypto_sw_scheduler/main.c @@ -575,11 +575,13 @@ crypto_sw_scheduler_config (vlib_main_t *vm, unformat_input_t *input) if (unformat (input, "crypto-sw-scheduler-queue-size %d", &crypto_sw_scheduler_queue_size)) { - if (!is_pow2 (crypto_sw_scheduler_queue_size)) + if (!crypto_sw_scheduler_queue_size || + !is_pow2 (crypto_sw_scheduler_queue_size) || + crypto_sw_scheduler_queue_size > VNET_CRYPTO_FRAME_POOL_SIZE) { - return clib_error_return (0, "input %d is not pow2", - format_unformat_error, - crypto_sw_scheduler_queue_size); + return clib_error_return ( + 0, "queue size must be a power of 2 between 1 and %u", + VNET_CRYPTO_FRAME_POOL_SIZE); } } else @@ -606,7 +608,7 @@ crypto_sw_scheduler_config (vlib_main_t *vm, unformat_input_t *input) vec_validate_aligned ( ptd->queue[CRYPTO_SW_SCHED_QUEUE_TYPE_DECRYPT].jobs, - CRYPTO_SW_SCHEDULER_QUEUE_SIZE - 1, CLIB_CACHE_LINE_BYTES); + crypto_sw_scheduler_queue_size - 1, CLIB_CACHE_LINE_BYTES); ptd->queue[CRYPTO_SW_SCHED_QUEUE_TYPE_ENCRYPT].head = 0; ptd->queue[CRYPTO_SW_SCHED_QUEUE_TYPE_ENCRYPT].tail = 0; @@ -616,7 +618,7 @@ crypto_sw_scheduler_config (vlib_main_t *vm, unformat_input_t *input) vec_validate_aligned ( ptd->queue[CRYPTO_SW_SCHED_QUEUE_TYPE_ENCRYPT].jobs, - CRYPTO_SW_SCHEDULER_QUEUE_SIZE - 1, CLIB_CACHE_LINE_BYTES); + crypto_sw_scheduler_queue_size - 1, CLIB_CACHE_LINE_BYTES); } if (error) diff --git a/src/plugins/dpdk/CMakeLists.txt b/src/plugins/dpdk/CMakeLists.txt index 1a81e04b9192..5c48bb933860 100644 --- a/src/plugins/dpdk/CMakeLists.txt +++ b/src/plugins/dpdk/CMakeLists.txt @@ -111,6 +111,7 @@ else() string_append(DPDK_LINK_FLAGS "-Wl,--whole-archive,${IBVERBS_LIB},${MLX5_LIB},${MLX4_LIB} -Wl,--no-whole-archive,--exclude-libs,ALL") endif() endif() + endif() ############################################################################## diff --git a/src/plugins/dpdk/device/common.c b/src/plugins/dpdk/device/common.c index 5731cfc42347..b7003a89905a 100644 --- a/src/plugins/dpdk/device/common.c +++ b/src/plugins/dpdk/device/common.c @@ -252,6 +252,9 @@ dpdk_device_setup (dpdk_device_t *xd) CLIB_CACHE_LINE_BYTES); for (j = 0; j < xd->conf.n_tx_queues; j++) { + dpdk_tx_queue_t *txq = vec_elt_at_index (xd->tx_queues, j); + + clib_spinlock_free (&txq->lock); rv = rte_eth_tx_queue_setup (xd->port_id, j, xd->conf.n_tx_desc, xd->cpu_socket, 0); @@ -262,7 +265,7 @@ dpdk_device_setup (dpdk_device_t *xd) if (rv < 0) dpdk_device_error (xd, "rte_eth_tx_queue_setup", rv); - clib_spinlock_init (&vec_elt (xd->tx_queues, j).lock); + clib_spinlock_init (&txq->lock); } vec_validate_aligned (xd->rx_queues, xd->conf.n_rx_queues - 1, @@ -364,6 +367,39 @@ dpdk_rx_read_ready (clib_file_t *uf) return 0; } +static void +dpdk_remove_interrupt_files (dpdk_device_t *xd) +{ + clib_file_main_t *fm = &file_main; + vnet_main_t *vnm = vnet_get_main (); + + for (int q = 0; q < vec_len (xd->rx_queues); q++) + { + dpdk_rx_queue_t *rxq = vec_elt_at_index (xd->rx_queues, q); + clib_file_t *f; + + if (rxq->clib_file_index == (uword) ~0) + continue; + + f = clib_file_get (fm, rxq->clib_file_index); + if (f && !rxq->clib_file_registered) + { + fm->file_update (f, UNIX_FILE_UPDATE_ADD); + rxq->clib_file_registered = 1; + } + /* The vnet rx queue keeps its own copy of the index for rx-placement + updates. Clear it before the file's pool slot is released, or the + next placement change on this queue resolves a freed file. */ + vnet_hw_if_set_rx_queue_file_index (vnm, rxq->queue_index, ~0); + clib_file_del_by_index (fm, rxq->clib_file_index); + rxq->clib_file_index = ~0; + rxq->clib_file_registered = 0; + rxq->efd = -1; + } + + xd->flags &= ~DPDK_DEVICE_FLAG_INT_SUPPORTED; +} + static void dpdk_setup_interrupts (dpdk_device_t *xd) { @@ -373,6 +409,8 @@ dpdk_setup_interrupts (dpdk_device_t *xd) if (!hi) return; + dpdk_remove_interrupt_files (xd); + if (!xd->conf.enable_rxq_int) return; @@ -408,10 +446,12 @@ dpdk_setup_interrupts (dpdk_device_t *xd) f.read_function = dpdk_rx_read_ready; f.flags = UNIX_FILE_EVENT_EDGE_TRIGGERED; f.file_descriptor = rxq->efd; + f.dont_close = 1; f.private_data = rxq->queue_index; f.description = format (0, "%U queue %u", format_dpdk_device_name, xd->device_index, q); rxq->clib_file_index = clib_file_add (&file_main, &f); + rxq->clib_file_registered = 1; vnet_hw_if_set_rx_queue_file_index (vnm, rxq->queue_index, rxq->clib_file_index); if (xd->flags & DPDK_DEVICE_FLAG_INT_UNMASKABLE) @@ -419,10 +459,14 @@ dpdk_setup_interrupts (dpdk_device_t *xd) clib_file_main_t *fm = &file_main; clib_file_t *f = clib_file_get (fm, rxq->clib_file_index); fm->file_update (f, UNIX_FILE_UPDATE_DELETE); + rxq->clib_file_registered = 0; } } } + if (!int_mode) + dpdk_remove_interrupt_files (xd); + if (int_mode) vnet_hw_if_set_caps (vnm, hi->hw_if_index, VNET_HW_IF_CAP_INT_MODE); else @@ -474,9 +518,14 @@ dpdk_device_start (dpdk_device_t * xd) void dpdk_device_stop (dpdk_device_t * xd) { + vnet_main_t *vnm = vnet_get_main (); + if (xd->flags & DPDK_DEVICE_FLAG_PMD_INIT_FAIL) return; + dpdk_remove_interrupt_files (xd); + vnet_hw_if_unset_caps (vnm, xd->hw_if_index, VNET_HW_IF_CAP_INT_MODE); + vnet_hw_if_update_runtime_data (vnm, xd->hw_if_index); rte_eth_allmulticast_disable (xd->port_id); rte_eth_dev_stop (xd->port_id); clib_memset (&xd->link, 0, sizeof (struct rte_eth_link)); diff --git a/src/plugins/dpdk/device/device.c b/src/plugins/dpdk/device/device.c index de44614938ee..455948c0ed3e 100644 --- a/src/plugins/dpdk/device/device.c +++ b/src/plugins/dpdk/device/device.c @@ -738,6 +738,7 @@ dpdk_interface_rx_mode_change (vnet_main_t *vnm, u32 hw_if_index, u32 qid, rxq = vec_elt_at_index (xd->rx_queues, qid); f = clib_file_get (fm, rxq->clib_file_index); fm->file_update (f, UNIX_FILE_UPDATE_DELETE); + rxq->clib_file_registered = 0; } else if (!(xd->flags & DPDK_DEVICE_FLAG_INT_UNMASKABLE)) rv = rte_eth_dev_rx_intr_enable (xd->port_id, qid); @@ -746,6 +747,7 @@ dpdk_interface_rx_mode_change (vnet_main_t *vnm, u32 hw_if_index, u32 qid, rxq = vec_elt_at_index (xd->rx_queues, qid); f = clib_file_get (fm, rxq->clib_file_index); fm->file_update (f, UNIX_FILE_UPDATE_ADD); + rxq->clib_file_registered = 1; } if (rv) return clib_error_return (0, "dpdk_interface_rx_mode_change: %U", format_dpdk_rte_err, rv); diff --git a/src/plugins/dpdk/device/dpdk.h b/src/plugins/dpdk/device/dpdk.h index 7b0225e5e369..5eb1c09f0809 100644 --- a/src/plugins/dpdk/device/dpdk.h +++ b/src/plugins/dpdk/device/dpdk.h @@ -95,6 +95,7 @@ typedef struct u32 queue_index; int efd; uword clib_file_index; + u8 clib_file_registered; } dpdk_rx_queue_t; typedef struct diff --git a/src/plugins/dpdk/device/init.c b/src/plugins/dpdk/device/init.c index 1741ce771723..a198e9115ffb 100644 --- a/src/plugins/dpdk/device/init.c +++ b/src/plugins/dpdk/device/init.c @@ -549,6 +549,14 @@ dpdk_lib_init (dpdk_main_t * dm) vec_validate_aligned (xd->tx_queues, xd->conf.n_tx_queues - 1, CLIB_CACHE_LINE_BYTES); + for (u32 q = 0; q < vec_len (xd->rx_queues); q++) + { + dpdk_rx_queue_t *rxq = vec_elt_at_index (xd->rx_queues, q); + rxq->efd = -1; + rxq->clib_file_index = ~0; + rxq->clib_file_registered = 0; + } + rte_eth_macaddr_get (port_id, (void *) addr); /* create interface */ @@ -830,6 +838,9 @@ dpdk_bind_devices_to_uio (dpdk_config_main_t * conf) /* Google vNIC */ else if (d->vendor_id == 0x1ae0 && d->device_id == 0x0042) ; + /* Huawei Hi1822 */ + else if (d->vendor_id == 0x19e5 && d->device_id == 0x1822) + ; else { dpdk_log_warn ("Unsupported PCI device 0x%04x:0x%04x found " diff --git a/src/plugins/flowprobe/FEATURE.yaml b/src/plugins/flowprobe/FEATURE.yaml index 9c80b12dc9f4..ee5c12212dd2 100644 --- a/src/plugins/flowprobe/FEATURE.yaml +++ b/src/plugins/flowprobe/FEATURE.yaml @@ -4,6 +4,7 @@ maintainer: Ole Troan features: - L2 input and output feature path - IPv4 / IPv6 input and output feature path + - L2 record on VLAN sub-interfaces for IPv4 / IPv6 traffic - Recording of L2, L3, and L4 information description: "IPFIX flow probe. Works in the L2 or IP feature path both input and output." missing: diff --git a/src/plugins/flowprobe/flowprobe.api b/src/plugins/flowprobe/flowprobe.api index c2090637cc82..d659da930b01 100644 --- a/src/plugins/flowprobe/flowprobe.api +++ b/src/plugins/flowprobe/flowprobe.api @@ -41,7 +41,7 @@ enum flowprobe_direction : u8 @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request @param is_add - add address if non-zero, else delete - @param which - flags indicating forwarding path + @param which - datapath on which to record flows (flowprobe_which_flags_t) @param sw_if_index - index of the interface */ autoreply define flowprobe_tx_interface_add_del diff --git a/src/plugins/flowprobe/flowprobe.c b/src/plugins/flowprobe/flowprobe.c index e8651e9270bc..bea1bc1c7326 100644 --- a/src/plugins/flowprobe/flowprobe.c +++ b/src/plugins/flowprobe/flowprobe.c @@ -77,6 +77,46 @@ VNET_FEATURE_INIT (flowprobe_output_l2, static) = { .runs_before = VNET_FEATURES ("interface-output-arc-end"), }; +/* + * On a VLAN sub-interface the L2 variant cannot use the device-input and + * interface-output arcs: both are evaluated against the physical interface + * index (device-input runs before ethernet-input has matched the tag, and + * interface-output uses the parent port's runtime index). Register the same + * node on the L3 input/output arcs instead; there VLIB_RX/VLIB_TX already + * hold the sub-interface index. flowprobe_feature_enable_disable() selects + * between the two sets based on vnet_sw_interface_is_sub(). + */ +VNET_FEATURE_INIT (flowprobe_input_l2_ip4_unicast, static) = { + .arc_name = "ip4-unicast", + .node_name = "flowprobe-input-l2", + .runs_before = VNET_FEATURES ("ip4-lookup", "ip4-inacl"), +}; +VNET_FEATURE_INIT (flowprobe_input_l2_ip4_multicast, static) = { + .arc_name = "ip4-multicast", + .node_name = "flowprobe-input-l2", + .runs_before = VNET_FEATURES ("ip4-mfib-forward-lookup"), +}; +VNET_FEATURE_INIT (flowprobe_input_l2_ip6_unicast, static) = { + .arc_name = "ip6-unicast", + .node_name = "flowprobe-input-l2", + .runs_before = VNET_FEATURES ("ip6-lookup", "ip6-inacl"), +}; +VNET_FEATURE_INIT (flowprobe_input_l2_ip6_multicast, static) = { + .arc_name = "ip6-multicast", + .node_name = "flowprobe-input-l2", + .runs_before = VNET_FEATURES ("ip6-mfib-forward-lookup"), +}; +VNET_FEATURE_INIT (flowprobe_output_l2_ip4, static) = { + .arc_name = "ip4-output", + .node_name = "flowprobe-output-l2", + .runs_before = VNET_FEATURES ("interface-output"), +}; +VNET_FEATURE_INIT (flowprobe_output_l2_ip6, static) = { + .arc_name = "ip6-output", + .node_name = "flowprobe-output-l2", + .runs_before = VNET_FEATURES ("interface-output"), +}; + #define FINISH \ vec_add1 (s, 0); \ vlib_cli_output (handle, (char *) s); \ @@ -307,7 +347,7 @@ flowprobe_template_rewrite_inline (ipfix_exporter_t *exp, flow_report_t *fr, /* Field count in this template */ t->id_count = ipfix_id_count (fr->template_id, f - first_field); - fm->template_size[flags] = (u8 *) f - (u8 *) s; + fm->template_size[which] = (u8 *) f - (u8 *) s; /* set length in octets */ s->set_id_length = @@ -428,6 +468,119 @@ flowprobe_template_add_del (u32 domain_id, u16 src_port, return vnet_flow_report_add_del (exp, &a, template_id); } +typedef struct +{ + /* Variant that owns the template slot. The raw record flags cannot be used + * as the slot key: IP4, IP6 and L2-plain all report the same flags value, so + * indexing the per-variant template tables by flags makes them overwrite + * each other. */ + flowprobe_variant_t variant; + vnet_flow_data_callback_t *flow_data_callback; + vnet_flow_rewrite_callback_t *rewrite_callback; +} flowprobe_template_spec_t; + +static u32 +flowprobe_get_template_specs (flowprobe_variant_t which, flowprobe_record_t flags, + flowprobe_template_spec_t specs[3]) +{ + u32 n_specs = 0; + + if (which == FLOW_VARIANT_L2) + { + if (flags & FLOW_RECORD_L2) + specs[n_specs++] = (flowprobe_template_spec_t) { + .variant = FLOW_VARIANT_L2, + .flow_data_callback = flowprobe_data_callback_l2, + .rewrite_callback = flowprobe_template_rewrite_l2, + }; + if (flags & FLOW_RECORD_L3 || flags & FLOW_RECORD_L4) + { + specs[n_specs++] = (flowprobe_template_spec_t) { + .variant = FLOW_VARIANT_L2_IP4, + .flow_data_callback = flowprobe_data_callback_l2, + .rewrite_callback = flowprobe_template_rewrite_l2_ip4, + }; + specs[n_specs++] = (flowprobe_template_spec_t) { + .variant = FLOW_VARIANT_L2_IP6, + .flow_data_callback = flowprobe_data_callback_l2, + .rewrite_callback = flowprobe_template_rewrite_l2_ip6, + }; + } + } + else if (which == FLOW_VARIANT_IP4) + specs[n_specs++] = (flowprobe_template_spec_t) { + .variant = FLOW_VARIANT_IP4, + .flow_data_callback = flowprobe_data_callback_ip4, + .rewrite_callback = flowprobe_template_rewrite_ip4, + }; + else if (which == FLOW_VARIANT_IP6) + specs[n_specs++] = (flowprobe_template_spec_t) { + .variant = FLOW_VARIANT_IP6, + .flow_data_callback = flowprobe_data_callback_ip6, + .rewrite_callback = flowprobe_template_rewrite_ip6, + }; + + return n_specs; +} + +static int +flowprobe_templates_add_del (flowprobe_main_t *fm, flowprobe_variant_t which, + flowprobe_record_t flags, bool is_add, bool *state_restored) +{ + flowprobe_template_spec_t specs[3]; + bool changed[3] = { 0 }; + u16 old_template_ids[3]; + u16 template_id; + u32 n_specs; + u32 i; + int rv; + + *state_restored = true; + n_specs = flowprobe_get_template_specs (which, flags, specs); + ASSERT (n_specs > 0); + + for (i = 0; i < n_specs; i++) + { + old_template_ids[i] = fm->template_reports[specs[i].variant]; + template_id = old_template_ids[i]; + rv = flowprobe_template_add_del (1, UDP_DST_PORT_ipfix, flags, specs[i].flow_data_callback, + specs[i].rewrite_callback, is_add, &template_id); + if (rv && !(is_add && rv == VNET_API_ERROR_VALUE_EXIST)) + goto rollback; + + changed[i] = rv == 0; + fm->template_reports[specs[i].variant] = is_add ? template_id : 0; + } + + return 0; + +rollback: + while (i > 0) + { + int rollback_rv; + + i--; + if (!changed[i]) + { + fm->template_reports[specs[i].variant] = old_template_ids[i]; + continue; + } + + template_id = old_template_ids[i]; + rollback_rv = + flowprobe_template_add_del (1, UDP_DST_PORT_ipfix, flags, specs[i].flow_data_callback, + specs[i].rewrite_callback, !is_add, &template_id); + if (rollback_rv && !(!is_add && rollback_rv == VNET_API_ERROR_VALUE_EXIST)) + { + *state_restored = false; + continue; + } + fm->template_reports[specs[i].variant] = is_add ? old_template_ids[i] : template_id; + } + + return rv; +} + static void flowprobe_expired_timer_callback (u32 * expired_timers) { @@ -532,6 +685,125 @@ flowprobe_clear_state_if_index (u32 sw_if_index) return error; } +static bool +flowprobe_wait_for_flush (vlib_main_t *vm, flowprobe_variant_t which, u64 generation) +{ + flowprobe_main_t *fm = &flowprobe_main; + f64 deadline = vlib_time_now (vm) + BARRIER_SYNC_TIMEOUT; + bool complete; + u32 i; + + vlib_worker_thread_barrier_release (vm); + do + { + complete = true; + for (i = 1; i < vlib_get_n_threads (); i++) + { + if (!vlib_get_main_by_index (i)) + continue; + if (clib_atomic_load_acq_n (&fm->flush_completed_generation_per_worker[which][i]) < + generation) + { + complete = false; + break; + } + } + if (!complete) + CLIB_PAUSE (); + } + while (!complete && vlib_time_now (vm) < deadline); + vlib_worker_thread_barrier_sync (vm); + + return complete; +} + +static u64 +flowprobe_flush_callback (flowprobe_variant_t which) +{ + if (which == FLOW_VARIANT_IP4) + return flowprobe_flush_callback_ip4 (); + if (which == FLOW_VARIANT_IP6) + return flowprobe_flush_callback_ip6 (); + + return flowprobe_flush_callback_l2 (); +} + +static void +flowprobe_feature_enable_disable (u32 sw_if_index, u8 which, u8 direction, int enable) +{ + flowprobe_main_t *fm = &flowprobe_main; + + /* Only meaningful on enable, where the interface type picks the arc set. On + disable both sets are cleared anyway, see below. */ + bool is_sub = enable && vnet_sw_interface_is_sub (fm->vnet_main, sw_if_index); + + if (direction == FLOW_DIRECTION_RX || direction == FLOW_DIRECTION_BOTH) + { + if (which == FLOW_VARIANT_IP4) + { + vnet_feature_enable_disable ("ip4-unicast", "flowprobe-input-ip4", sw_if_index, enable, 0, + 0); + vnet_feature_enable_disable ("ip4-multicast", "flowprobe-input-ip4", sw_if_index, enable, + 0, 0); + } + else if (which == FLOW_VARIANT_IP6) + { + vnet_feature_enable_disable ("ip6-unicast", "flowprobe-input-ip6", sw_if_index, enable, 0, + 0); + vnet_feature_enable_disable ("ip6-multicast", "flowprobe-input-ip6", sw_if_index, enable, + 0, 0); + } + else if (which == FLOW_VARIANT_L2) + { + /* + * A VLAN sub-interface is reached through the L3 feature arcs, a + * physical port through device-input. On disable both sets are + * cleared: the interface may have changed type, or its index may have + * been reused, since the feature was enabled, and clearing a feature + * that was never set on an arc is a no-op. + */ + if (!enable || is_sub) + { + vnet_feature_enable_disable ("ip4-unicast", "flowprobe-input-l2", sw_if_index, enable, + 0, 0); + vnet_feature_enable_disable ("ip4-multicast", "flowprobe-input-l2", sw_if_index, + enable, 0, 0); + vnet_feature_enable_disable ("ip6-unicast", "flowprobe-input-l2", sw_if_index, enable, + 0, 0); + vnet_feature_enable_disable ("ip6-multicast", "flowprobe-input-l2", sw_if_index, + enable, 0, 0); + } + if (!enable || !is_sub) + vnet_feature_enable_disable ("device-input", "flowprobe-input-l2", sw_if_index, enable, + 0, 0); + } + } + + if (direction == FLOW_DIRECTION_TX || direction == FLOW_DIRECTION_BOTH) + { + if (which == FLOW_VARIANT_IP4) + vnet_feature_enable_disable ("ip4-output", "flowprobe-output-ip4", sw_if_index, enable, 0, + 0); + else if (which == FLOW_VARIANT_IP6) + vnet_feature_enable_disable ("ip6-output", "flowprobe-output-ip6", sw_if_index, enable, 0, + 0); + else if (which == FLOW_VARIANT_L2) + { + /* Same split as RX above, see flowprobe-input-l2. */ + if (!enable || is_sub) + { + vnet_feature_enable_disable ("ip4-output", "flowprobe-output-l2", sw_if_index, enable, + 0, 0); + vnet_feature_enable_disable ("ip6-output", "flowprobe-output-l2", sw_if_index, enable, + 0, 0); + } + if (!enable || !is_sub) + vnet_feature_enable_disable ("interface-output", "flowprobe-output-l2", sw_if_index, + enable, 0, 0); + } + } +} + static int validate_feature_on_interface (flowprobe_main_t * fm, u32 sw_if_index, u8 which) @@ -562,138 +834,92 @@ flowprobe_interface_add_del_feature (flowprobe_main_t *fm, u32 sw_if_index, u8 which, u8 direction, int is_add) { vlib_main_t *vm = vlib_get_main (); + bool template_state_restored = true; + bool update_template; int rv = 0; - u16 template_id = 0; flowprobe_record_t flags = fm->record; + u64 flush_generation; + + if (!is_add) + direction = fm->direction_per_interface[sw_if_index]; - fm->flow_per_interface[sw_if_index] = (is_add) ? which : (u8) ~ 0; - fm->direction_per_interface[sw_if_index] = (is_add) ? direction : (u8) ~0; - fm->template_per_flow[which] += (is_add) ? 1 : -1; - if (is_add && fm->template_per_flow[which] > 1) - template_id = fm->template_reports[flags]; + vlib_worker_thread_barrier_sync (vm); - if ((is_add && fm->template_per_flow[which] == 1) || - (!is_add && fm->template_per_flow[which] == 0)) + update_template = + (is_add && fm->template_per_flow[which] == 0) || (!is_add && fm->template_per_flow[which] == 1); + + if (!is_add) { - if (which == FLOW_VARIANT_L2) - { - if (!is_add) - { - flowprobe_flush_callback_l2 (); - } - if (fm->record & FLOW_RECORD_L2) - { - rv = flowprobe_template_add_del (1, UDP_DST_PORT_ipfix, flags, - flowprobe_data_callback_l2, - flowprobe_template_rewrite_l2, - is_add, &template_id); - fm->template_reports[flags] = (is_add) ? template_id : 0; - } - if (fm->record & FLOW_RECORD_L3 || fm->record & FLOW_RECORD_L4) - { - rv = flowprobe_template_add_del (1, UDP_DST_PORT_ipfix, flags, - flowprobe_data_callback_l2, - flowprobe_template_rewrite_l2_ip4, - is_add, &template_id); - fm->template_reports[flags | FLOW_RECORD_L2_IP4] = - (is_add) ? template_id : 0; - rv = - flowprobe_template_add_del (1, UDP_DST_PORT_ipfix, flags, - flowprobe_data_callback_l2, - flowprobe_template_rewrite_l2_ip6, - is_add, &template_id); - fm->template_reports[flags | FLOW_RECORD_L2_IP6] = - (is_add) ? template_id : 0; - - /* Special case L2 */ - fm->context[FLOW_VARIANT_L2_IP4].flags = - flags | FLOW_RECORD_L2_IP4; - fm->context[FLOW_VARIANT_L2_IP6].flags = - flags | FLOW_RECORD_L2_IP6; - } - } - else if (which == FLOW_VARIANT_IP4) - { - if (!is_add) - { - flowprobe_flush_callback_ip4 (); - } - rv = flowprobe_template_add_del ( - 1, UDP_DST_PORT_ipfix, flags, flowprobe_data_callback_ip4, - flowprobe_template_rewrite_ip4, is_add, &template_id); - fm->template_reports[flags] = (is_add) ? template_id : 0; - } - else if (which == FLOW_VARIANT_IP6) + flowprobe_feature_enable_disable (sw_if_index, which, direction, 0); + if (update_template) { - if (!is_add) + clib_atomic_store_rel_n (&fm->flush_in_progress, 1); + flush_generation = flowprobe_flush_callback (which); + if (!flowprobe_wait_for_flush (vm, which, flush_generation)) { - flowprobe_flush_callback_ip6 (); + clib_warning ("timed out waiting for flowprobe worker flush"); + clib_atomic_store_rel_n (&fm->flush_in_progress, 0); + flowprobe_feature_enable_disable (sw_if_index, which, direction, 1); + rv = VNET_API_ERROR_BUSY; + goto out; } - rv = flowprobe_template_add_del ( - 1, UDP_DST_PORT_ipfix, flags, flowprobe_data_callback_ip6, - flowprobe_template_rewrite_ip6, is_add, &template_id); - fm->template_reports[flags] = (is_add) ? template_id : 0; } } - if (rv && rv != VNET_API_ERROR_VALUE_EXIST) - { - clib_warning ("vnet_flow_report_add_del returned %d", rv); - return -1; - } - - if (which != (u8) ~ 0) - { - fm->context[which].flags = fm->record; - } - if (direction == FLOW_DIRECTION_RX || direction == FLOW_DIRECTION_BOTH) + if (update_template) + rv = flowprobe_templates_add_del (fm, which, flags, is_add, &template_state_restored); + if (rv) { - if (which == FLOW_VARIANT_IP4) - { - vnet_feature_enable_disable ("ip4-unicast", "flowprobe-input-ip4", - sw_if_index, is_add, 0, 0); - vnet_feature_enable_disable ("ip4-multicast", "flowprobe-input-ip4", - sw_if_index, is_add, 0, 0); - } - else if (which == FLOW_VARIANT_IP6) + clib_warning ("vnet_flow_report_add_del returned %d", rv); + if (!is_add) { - vnet_feature_enable_disable ("ip6-unicast", "flowprobe-input-ip6", - sw_if_index, is_add, 0, 0); - vnet_feature_enable_disable ("ip6-multicast", "flowprobe-input-ip6", - sw_if_index, is_add, 0, 0); + if (template_state_restored) + flowprobe_feature_enable_disable (sw_if_index, which, direction, 1); + else + { + fm->flow_per_interface[sw_if_index] = (u8) ~0; + fm->direction_per_interface[sw_if_index] = (u8) ~0; + fm->template_per_flow[which]--; + flowprobe_clear_state_if_index (sw_if_index); + } + clib_atomic_store_rel_n (&fm->flush_in_progress, 0); } - else if (which == FLOW_VARIANT_L2) - vnet_feature_enable_disable ("device-input", "flowprobe-input-l2", - sw_if_index, is_add, 0, 0); + rv = -1; + goto out; } - if (direction == FLOW_DIRECTION_TX || direction == FLOW_DIRECTION_BOTH) + fm->flow_per_interface[sw_if_index] = is_add ? which : (u8) ~0; + fm->direction_per_interface[sw_if_index] = is_add ? direction : (u8) ~0; + fm->template_per_flow[which] += is_add ? 1 : -1; + fm->context[which].flags = fm->record; + if (which == FLOW_VARIANT_L2 && (flags & FLOW_RECORD_L3 || flags & FLOW_RECORD_L4)) { - if (which == FLOW_VARIANT_IP4) - vnet_feature_enable_disable ("ip4-output", "flowprobe-output-ip4", - sw_if_index, is_add, 0, 0); - else if (which == FLOW_VARIANT_IP6) - vnet_feature_enable_disable ("ip6-output", "flowprobe-output-ip6", - sw_if_index, is_add, 0, 0); - else if (which == FLOW_VARIANT_L2) - vnet_feature_enable_disable ("interface-output", "flowprobe-output-l2", - sw_if_index, is_add, 0, 0); + fm->context[FLOW_VARIANT_L2_IP4].flags = flags | FLOW_RECORD_L2_IP4; + fm->context[FLOW_VARIANT_L2_IP6].flags = flags | FLOW_RECORD_L2_IP6; } - /* Stateful flow collection */ - if (is_add && !fm->initialized) + if (is_add) { - flowprobe_create_state_tables (fm->active_timer); - if (fm->active_timer) - vlib_process_signal_event (vm, flowprobe_timer_node.index, 1, 0); + if (!fm->initialized || (fm->active_timer && !fm->hash_per_worker)) + { + flowprobe_create_state_tables (fm->active_timer); + if (fm->active_timer) + vlib_process_signal_event (vm, flowprobe_timer_node.index, 1, 0); + } + flowprobe_feature_enable_disable (sw_if_index, which, direction, 1); } - - if (!is_add && fm->initialized) + else { flowprobe_clear_state_if_index (sw_if_index); + if (update_template) + clib_atomic_store_rel_n (&fm->flush_in_progress, 0); } - return 0; + rv = 0; + +out: + vlib_worker_thread_barrier_release (vm); + return rv; } /** @@ -710,6 +936,19 @@ void vl_api_flowprobe_tx_interface_add_del_t_handler VALIDATE_SW_IF_INDEX (mp); + /* + * The field is documented as a bitmask of forwarding paths and typed as one, + * but it is passed to the datapath as a flowprobe_variant_t. The two sets + * of values only overlap on 0, 1 and 2; the rest either name a variant this + * message cannot reach or fall outside the variant arrays, so reject them. + */ + if ((u8) mp->which > (u8) FLOW_VARIANT_L2) + { + clib_warning ("Invalid value of which"); + rv = VNET_API_ERROR_INVALID_VALUE; + goto out; + } + if (fm->record == 0) { clib_warning ("Please specify flowprobe params record first..."); @@ -718,7 +957,7 @@ void vl_api_flowprobe_tx_interface_add_del_t_handler } rv = validate_feature_on_interface (fm, sw_if_index, mp->which); - if ((rv == 1 && mp->is_add == 1) || rv == 0) + if ((rv == 1 && mp->is_add == 1) || rv == 0 || (rv == -1 && mp->is_add == 0)) { rv = VNET_API_ERROR_CANNOT_ENABLE_DISABLE_FEATURE; goto out; @@ -1312,6 +1551,7 @@ VLIB_CLI_COMMAND (flowprobe_enable_disable_command, static) = { .short_help = "flowprobe feature add-del [(l2|ip4|ip6)] " "[(rx|tx|both)] [disable]", .function = flowprobe_interface_add_del_feature_command_fn, + .is_mp_safe = 1, }; VLIB_CLI_COMMAND (flowprobe_params_command, static) = { .path = "flowprobe params", @@ -1413,6 +1653,7 @@ flowprobe_init (vlib_main_t * vm) { flowprobe_main_t *fm = &flowprobe_main; vlib_thread_main_t *tm = &vlib_thread_main; + api_main_t *am = vlibapi_get_main (); clib_error_t *error = 0; u32 num_threads; int i; @@ -1421,6 +1662,8 @@ flowprobe_init (vlib_main_t * vm) /* Ask for a correctly-sized block of API message decode slots */ fm->msg_id_base = setup_message_id_table (); + vl_api_set_msg_thread_safe (am, fm->msg_id_base + VL_API_FLOWPROBE_TX_INTERFACE_ADD_DEL, 1); + vl_api_set_msg_thread_safe (am, fm->msg_id_base + VL_API_FLOWPROBE_INTERFACE_ADD_DEL, 1); /* Set up time reference pair */ fm->vlib_time_0 = vlib_time_now (vm); @@ -1436,6 +1679,7 @@ flowprobe_init (vlib_main_t * vm) /* Allocate per worker thread vectors per flavour */ for (i = 0; i < FLOW_N_VARIANTS; i++) { + vec_validate (fm->flush_completed_generation_per_worker[i], num_threads - 1); vec_validate (fm->context[i].buffers_per_worker, num_threads - 1); vec_validate (fm->context[i].frames_per_worker, num_threads - 1); vec_validate (fm->context[i].next_record_offset_per_worker, diff --git a/src/plugins/flowprobe/flowprobe.h b/src/plugins/flowprobe/flowprobe.h index abe9486f90db..3f13335512ea 100644 --- a/src/plugins/flowprobe/flowprobe.h +++ b/src/plugins/flowprobe/flowprobe.h @@ -29,7 +29,6 @@ typedef enum FLOW_RECORD_L4 = 1 << 2, FLOW_RECORD_L2_IP4 = 1 << 3, FLOW_RECORD_L2_IP6 = 1 << 4, - FLOW_N_RECORDS = 1 << 5, } flowprobe_record_t; typedef enum __attribute__ ((__packed__)) @@ -117,8 +116,11 @@ typedef struct u16 msg_id_base; flowprobe_protocol_context_t context[FLOW_N_VARIANTS]; - u16 template_reports[FLOW_N_RECORDS]; - u16 template_size[FLOW_N_RECORDS]; + /* Indexed by flowprobe_variant_t. Each variant owns an independent + * template slot so that IP4, IP6, L2, L2_IP4 and L2_IP6 cannot overwrite + * one another when they share the same record flags. */ + u16 template_reports[FLOW_N_VARIANTS]; + u16 template_size[FLOW_N_VARIANTS]; /** Time reference pair */ u64 nanosecond_time_0; @@ -130,6 +132,8 @@ typedef struct flowprobe_entry_t **pool_per_worker; TWT (tw_timer_wheel) ** timers_per_worker; u32 **expired_passive_per_worker; + u64 *flush_completed_generation_per_worker[FLOW_N_VARIANTS]; + u64 flush_generation[FLOW_N_VARIANTS]; flowprobe_record_t record; u32 active_timer; @@ -138,6 +142,7 @@ typedef struct bool initialized; bool disabled; + u32 flush_in_progress; u16 template_per_flow[FLOW_N_VARIANTS]; u8 *flow_per_interface; @@ -154,9 +159,9 @@ extern vlib_node_registration_t flowprobe_walker_node; void flowprobe_delete_by_index (u32 my_cpu_number, u32 poolindex); -void flowprobe_flush_callback_ip4 (void); -void flowprobe_flush_callback_ip6 (void); -void flowprobe_flush_callback_l2 (void); +u64 flowprobe_flush_callback_ip4 (void); +u64 flowprobe_flush_callback_ip6 (void); +u64 flowprobe_flush_callback_l2 (void); u8 *format_flowprobe_entry (u8 * s, va_list * args); #endif diff --git a/src/plugins/flowprobe/flowprobe_plugin_doc.rst b/src/plugins/flowprobe/flowprobe_plugin_doc.rst index 4add41f5611f..eeb6ad7e8a6b 100644 --- a/src/plugins/flowprobe/flowprobe_plugin_doc.rst +++ b/src/plugins/flowprobe/flowprobe_plugin_doc.rst @@ -16,3 +16,13 @@ Sample configuration flowprobe params record l3 active 20 passive 120 flowprobe feature add-del GigabitEthernet2/3/0 l2 + +The ``l2`` datapath can also be enabled on a VLAN sub-interface, where it is +installed on the IPv4 and IPv6 feature arcs of that sub-interface. When the +record includes ``l2``, it records the MAC addresses and the inner ethertype +of IPv4 and IPv6 frames of the enabled directions. Non-IP frames are not +recorded on a sub-interface. + +:: + + flowprobe feature add-del GigabitEthernet2/3/0.10 l2 both diff --git a/src/plugins/flowprobe/node.c b/src/plugins/flowprobe/node.c index 6faa428eb84e..a4159366881f 100644 --- a/src/plugins/flowprobe/node.c +++ b/src/plugins/flowprobe/node.c @@ -132,6 +132,53 @@ typedef enum [FLOWPROBE_NEXT_IP4_LOOKUP] = "ip4-lookup", \ } +/* + * The L2 variant runs on two different kinds of arc. On device-input / + * interface-output (physical ports) current_data points at the Ethernet + * header. On the L3 feature arcs ethernet-input has already consumed the L2 + * header on RX, so the Ethernet header must be taken from l2_hdr_offset; on TX + * the rewritten header is again at current_data. + */ +static_always_inline ethernet_header_t * +flowprobe_eth_header (vlib_buffer_t *b, flowprobe_direction_t direction) +{ + if (direction == FLOW_DIRECTION_RX && (b->flags & VNET_BUFFER_F_L2_HDR_OFFSET_VALID)) + return ethernet_buffer_get_header (b); + return vlib_buffer_get_current (b); +} + +/* + * Walk the (possibly stacked) VLAN tags and return the innermost ethertype, + * counting the tags in *n_tags. The walk stops at the end of the frame, so a + * truncated tag header is never read. + */ +static_always_inline u16 +flowprobe_vlan_walk (vlib_buffer_t *b, flowprobe_direction_t direction, u16 *n_tags) +{ + ethernet_header_t *eth = flowprobe_eth_header (b, direction); + ethernet_vlan_header_tv_t *ethv = (ethernet_vlan_header_tv_t *) ð->type; + u8 *end = b->data + b->current_data + b->current_length; + u16 ethertype = clib_net_to_host_u16 (ethv->type); + + *n_tags = 0; + while (ethernet_frame_is_tagged (ethertype) && (u8 *) (ethv + 1) + sizeof (ethv->type) <= end) + { + ethv++; + (*n_tags)++; + ethertype = clib_net_to_host_u16 (ethv->type); + } + return ethertype; +} + +/* Innermost ethertype, skipping any (stacked) VLAN tags. */ +static_always_inline u16 +flowprobe_inner_ethertype (vlib_buffer_t *b, flowprobe_direction_t direction) +{ + u16 n_tags; + + return flowprobe_vlan_walk (b, direction, &n_tags); +} + static inline flowprobe_variant_t flowprobe_get_variant (flowprobe_variant_t which, flowprobe_record_t flags, u16 ethertype) @@ -377,11 +424,11 @@ add_to_flow_record_state (vlib_main_t *vm, vlib_node_runtime_t *node, flowprobe_record_t flags = fm->context[which].flags; bool collect_ip4 = false, collect_ip6 = false; ASSERT (b); - ethernet_header_t *eth = (direction == FLOW_DIRECTION_TX) ? - vlib_buffer_get_current (b) : - ethernet_buffer_get_header (b); - u16 ethertype = clib_net_to_host_u16 (eth->type); - i16 l3_hdr_offset = (u8 *) eth - b->data + sizeof (ethernet_header_t); + ethernet_header_t *eth = flowprobe_eth_header (b, direction); + u16 n_tags = 0; + u16 ethertype = flowprobe_vlan_walk (b, direction, &n_tags); + i16 l3_hdr_offset = + (u8 *) eth - b->data + sizeof (ethernet_header_t) + n_tags * sizeof (ethernet_vlan_header_tv_t); flowprobe_key_t k = {}; ip4_header_t *ip4 = 0; ip6_header_t *ip6 = 0; @@ -400,25 +447,12 @@ add_to_flow_record_state (vlib_main_t *vm, vlib_node_runtime_t *node, k.which = which; k.direction = direction; + k.ethertype = ethertype; if (flags & FLOW_RECORD_L2) { clib_memcpy_fast (k.src_mac, eth->src_address, 6); clib_memcpy_fast (k.dst_mac, eth->dst_address, 6); - k.ethertype = ethertype; - } - if (ethertype == ETHERNET_TYPE_VLAN) - { - /*VLAN TAG*/ - ethernet_vlan_header_tv_t *ethv = - (ethernet_vlan_header_tv_t *) (&(eth->type)); - /*Q in Q possibility */ - while (clib_net_to_host_u16 (ethv->type) == ETHERNET_TYPE_VLAN) - { - ethv++; - l3_hdr_offset += sizeof (ethernet_vlan_header_tv_t); - } - k.ethertype = ethertype = clib_net_to_host_u16 ((ethv)->type); } if (collect_ip6 && ethertype == ETHERNET_TYPE_IP6) { @@ -548,7 +582,6 @@ flowprobe_export_send (vlib_main_t * vm, vlib_buffer_t * b0, ipfix_message_header_t *h; ip4_header_t *ip; udp_header_t *udp; - flowprobe_record_t flags = fm->context[which].flags; u32 my_cpu_number = vm->thread_index; /* Fill in header */ @@ -599,10 +632,8 @@ flowprobe_export_send (vlib_main_t * vm, vlib_buffer_t * b0, h->sequence_number = stream->sequence_number++; h->sequence_number = clib_host_to_net_u32 (h->sequence_number); - s->set_id_length = ipfix_set_id_length (fm->template_reports[flags], - b0->current_length - - (sizeof (*ip) + sizeof (*udp) + - sizeof (*h))); + s->set_id_length = ipfix_set_id_length ( + fm->template_reports[which], b0->current_length - (sizeof (*ip) + sizeof (*udp) + sizeof (*h))); h->version_length = version_length (b0->current_length - (sizeof (*ip) + sizeof (*udp))); @@ -738,7 +769,7 @@ flowprobe_export_entry (vlib_main_t * vm, flowprobe_entry_t * e) fm->context[which].next_record_offset_per_worker[my_cpu_number] = offset; /* Time to flush the buffer? */ - if (offset + fm->template_size[flags] > exp->path_mtu) + if (offset + fm->template_size[which] > exp->path_mtu) flowprobe_export_send (vm, b0, which); } @@ -801,8 +832,7 @@ flowprobe_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vnet_feature_next (&next1, b1); len0 = vlib_buffer_length_in_chain (vm, b0); - ethernet_header_t *eh0 = vlib_buffer_get_current (b0); - u16 ethertype0 = clib_net_to_host_u16 (eh0->type); + u16 ethertype0 = flowprobe_inner_ethertype (b0, direction); if (PREDICT_TRUE ((b0->flags & VNET_BUFFER_F_FLOW_REPORT) == 0)) add_to_flow_record_state ( @@ -812,8 +842,7 @@ flowprobe_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, direction, 0); len1 = vlib_buffer_length_in_chain (vm, b1); - ethernet_header_t *eh1 = vlib_buffer_get_current (b1); - u16 ethertype1 = clib_net_to_host_u16 (eh1->type); + u16 ethertype1 = flowprobe_inner_ethertype (b1, direction); if (PREDICT_TRUE ((b1->flags & VNET_BUFFER_F_FLOW_REPORT) == 0)) add_to_flow_record_state ( @@ -848,8 +877,7 @@ flowprobe_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, vnet_feature_next (&next0, b0); len0 = vlib_buffer_length_in_chain (vm, b0); - ethernet_header_t *eh0 = vlib_buffer_get_current (b0); - u16 ethertype0 = clib_net_to_host_u16 (eh0->type); + u16 ethertype0 = flowprobe_inner_ethertype (b0, direction); if (PREDICT_TRUE ((b0->flags & VNET_BUFFER_F_FLOW_REPORT) == 0)) { @@ -925,18 +953,37 @@ flowprobe_output_l2_node_fn (vlib_main_t *vm, vlib_node_runtime_t *node, } static inline void -flush_record (flowprobe_variant_t which) +flush_record (vlib_main_t *vm, flowprobe_variant_t which) { - vlib_main_t *vm = vlib_get_main (); vlib_buffer_t *b = flowprobe_get_buffer (vm, which); if (b) flowprobe_export_send (vm, b, which); } -void +static u64 +flowprobe_flush_generation_next (flowprobe_variant_t which) +{ + flowprobe_main_t *fm = &flowprobe_main; + + return clib_atomic_add_fetch (&fm->flush_generation[which], 1); +} + +static void +flowprobe_flush_complete (vlib_main_t *vm, flowprobe_variant_t which) +{ + flowprobe_main_t *fm = &flowprobe_main; + u64 generation = clib_atomic_load_acq_n (&fm->flush_generation[which]); + + clib_atomic_store_rel_n (&fm->flush_completed_generation_per_worker[which][vm->thread_index], + generation); +} + +u64 flowprobe_flush_callback_ip4 (void) { + vlib_main_t *vm = vlib_get_main (); vlib_main_t *worker_vm; + u64 generation = flowprobe_flush_generation_next (FLOW_VARIANT_IP4); u32 i; /* Flush for each worker thread */ @@ -949,13 +996,16 @@ flowprobe_flush_callback_ip4 (void) } /* Flush for the main thread */ - flush_record (FLOW_VARIANT_IP4); + flush_record (vm, FLOW_VARIANT_IP4); + return generation; } -void +u64 flowprobe_flush_callback_ip6 (void) { + vlib_main_t *vm = vlib_get_main (); vlib_main_t *worker_vm; + u64 generation = flowprobe_flush_generation_next (FLOW_VARIANT_IP6); u32 i; /* Flush for each worker thread */ @@ -968,13 +1018,16 @@ flowprobe_flush_callback_ip6 (void) } /* Flush for the main thread */ - flush_record (FLOW_VARIANT_IP6); + flush_record (vm, FLOW_VARIANT_IP6); + return generation; } -void +u64 flowprobe_flush_callback_l2 (void) { + vlib_main_t *vm = vlib_get_main (); vlib_main_t *worker_vm; + u64 generation = flowprobe_flush_generation_next (FLOW_VARIANT_L2); u32 i; /* Flush for each worker thread */ @@ -987,9 +1040,10 @@ flowprobe_flush_callback_l2 (void) } /* Flush for the main thread */ - flush_record (FLOW_VARIANT_L2); - flush_record (FLOW_VARIANT_L2_IP4); - flush_record (FLOW_VARIANT_L2_IP6); + flush_record (vm, FLOW_VARIANT_L2); + flush_record (vm, FLOW_VARIANT_L2_IP4); + flush_record (vm, FLOW_VARIANT_L2_IP6); + return generation; } void @@ -1020,6 +1074,9 @@ flowprobe_walker_process (vlib_main_t * vm, flowprobe_entry_t *e; ipfix_exporter_t *exp = pool_elt_at_index (flow_report_main.exporters, 0); + if (clib_atomic_load_acq_n (&fm->flush_in_progress)) + return 0; + /* * $$$$ Remove this check from here and track FRM status and disable * this process if required. @@ -1095,7 +1152,8 @@ flowprobe_walker_process (vlib_main_t * vm, static uword flowprobe_flush_ip4 (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) { - flush_record (FLOW_VARIANT_IP4); + flush_record (vm, FLOW_VARIANT_IP4); + flowprobe_flush_complete (vm, FLOW_VARIANT_IP4); return 0; } @@ -1103,7 +1161,8 @@ flowprobe_flush_ip4 (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) static uword flowprobe_flush_ip6 (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) { - flush_record (FLOW_VARIANT_IP6); + flush_record (vm, FLOW_VARIANT_IP6); + flowprobe_flush_complete (vm, FLOW_VARIANT_IP6); return 0; } @@ -1111,9 +1170,10 @@ flowprobe_flush_ip6 (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) static uword flowprobe_flush_l2 (vlib_main_t *vm, vlib_node_runtime_t *rt, vlib_frame_t *f) { - flush_record (FLOW_VARIANT_L2); - flush_record (FLOW_VARIANT_L2_IP4); - flush_record (FLOW_VARIANT_L2_IP6); + flush_record (vm, FLOW_VARIANT_L2); + flush_record (vm, FLOW_VARIANT_L2_IP4); + flush_record (vm, FLOW_VARIANT_L2_IP6); + flowprobe_flush_complete (vm, FLOW_VARIANT_L2); return 0; } diff --git a/src/plugins/gre/gre.c b/src/plugins/gre/gre.c index dcb44010aca6..b8a98784ed3e 100644 --- a/src/plugins/gre/gre.c +++ b/src/plugins/gre/gre.c @@ -194,29 +194,51 @@ gre_proto_from_vnet_link (vnet_link_t link) return (GRE_PROTOCOL_ip4); } +/* + * The mapping holds an entry for the tunnel's own interface only, so a + * sub-interface of a tunnel is not one of ours; indexing the vector with such + * an index reads past its end and has to be rejected first. + */ +static gre_tunnel_t * +gre_tunnel_find_by_sw_if_index (u32 sw_if_index) +{ + gre_main_t *gm = &gre_main; + u32 ti; + + if (vec_len (gm->tunnel_index_by_sw_if_index) <= sw_if_index) + return (0); + + ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; + + if (~0 == ti) + /* not one of ours */ + return (0); + + if (pool_is_free_index (gm->tunnels, ti)) + return (0); + + return (pool_elt_at_index (gm->tunnels, ti)); +} + static u8 * gre_build_rewrite (vnet_main_t *vnm, u32 sw_if_index, vnet_link_t link_type, const void *dst_address) { - gre_main_t *gm = &gre_main; const ip46_address_t *dst; ip4_and_gre_header_t *h4; ip6_and_gre_header_t *h6; gre_header_t *gre; u8 *rewrite = NULL; gre_tunnel_t *t; - u32 ti; u8 is_ipv6; dst = dst_address; - ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; + t = gre_tunnel_find_by_sw_if_index (sw_if_index); - if (~0 == ti) + if (0 == t) /* not one of ours */ return (0); - t = pool_elt_at_index (gm->tunnels, ti); - is_ipv6 = t->tunnel_dst.fp_proto == FIB_PROTOCOL_IP6 ? 1 : 0; if (!is_ipv6) @@ -402,13 +424,14 @@ gre_get_fixup (fib_protocol_t fproto, vnet_link_t lt) void gre_update_adj (vnet_main_t *vnm, u32 sw_if_index, adj_index_t ai) { - gre_main_t *gm = &gre_main; gre_tunnel_t *t; adj_flags_t af; - u32 ti; - ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; - t = pool_elt_at_index (gm->tunnels, ti); + t = gre_tunnel_find_by_sw_if_index (sw_if_index); + + if (0 == t) + return; + af = ADJ_FLAG_NONE; /* @@ -473,15 +496,16 @@ mgre_mk_incomplete_walk (adj_index_t ai, void *data) void mgre_update_adj (vnet_main_t *vnm, u32 sw_if_index, adj_index_t ai) { - gre_main_t *gm = &gre_main; ip_adjacency_t *adj; teib_entry_t *ne; gre_tunnel_t *t; - u32 ti; + + t = gre_tunnel_find_by_sw_if_index (sw_if_index); + + if (0 == t) + return; adj = adj_get (ai); - ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; - t = pool_elt_at_index (gm->tunnels, ti); ne = teib_entry_find_46 (sw_if_index, adj->ia_nh_proto, &adj->sub_type.nbr.next_hop); @@ -728,18 +752,14 @@ static int gre_tunnel_desc (u32 sw_if_index, ip46_address_t *src, ip46_address_t *dst, u8 *is_l2) { - gre_main_t *gm = &gre_main; gre_tunnel_t *t; - u32 ti; - ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; + t = gre_tunnel_find_by_sw_if_index (sw_if_index); - if (~0 == ti) + if (0 == t) /* not one of ours */ return -1; - t = pool_elt_at_index (gm->tunnels, ti); - *src = t->tunnel_src; *dst = t->tunnel_dst.fp_addr; *is_l2 = t->type == GRE_TUNNEL_TYPE_TEB; diff --git a/src/plugins/gre/interface.c b/src/plugins/gre/interface.c index 1d2dfba61751..35ce18e64674 100644 --- a/src/plugins/gre/interface.c +++ b/src/plugins/gre/interface.c @@ -483,11 +483,13 @@ vnet_gre_tunnel_add (vnet_gre_tunnel_add_del_args_t *a, u32 outer_fib_index, { gre_sn_key_t skey; gre_sn_t *gre_sn; + uword *p; gre_mk_sn_key (t, &skey); - gre_sn = (gre_sn_t *) hash_get_mem (gm->seq_num_by_key, &skey); - if (gre_sn != NULL) + p = hash_get_mem (gm->seq_num_by_key, &skey); + if (p != NULL) { + gre_sn = (gre_sn_t *) p[0]; gre_sn->ref_count++; t->gre_sn = gre_sn; } @@ -594,6 +596,9 @@ vnet_gre_tunnel_add_del (vnet_gre_tunnel_add_del_args_t *a, u32 *sw_if_indexp) if (a->session_id > GTK_SESSION_ID_MAX) return VNET_API_ERROR_INVALID_SESSION_ID; + if (a->is_add && a->session_id != 0 && a->type != GRE_TUNNEL_TYPE_ERSPAN) + return VNET_API_ERROR_INVALID_SESSION_ID; + if (a->mode == TUNNEL_MODE_MP && !ip46_address_is_zero (&a->dst)) return (VNET_API_ERROR_INVALID_DST_ADDRESS); diff --git a/src/plugins/gre/node.c b/src/plugins/gre/node.c index 80926276913b..04cb90d0d0a5 100644 --- a/src/plugins/gre/node.c +++ b/src/plugins/gre/node.c @@ -450,7 +450,7 @@ gre_input (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame, vlib_node_increment_counter ( vm, is_ipv6 ? gre6_input_node.index : gre4_input_node.index, - GRE_ERROR_PKTS_DECAP, n_left_from); + GRE_ERROR_PKTS_DECAP, frame->n_vectors); return frame->n_vectors; } diff --git a/src/plugins/http/http1.c b/src/plugins/http/http1.c index 13c2d5a07583..d3b94dd384e4 100644 --- a/src/plugins/http/http1.c +++ b/src/plugins/http/http1.c @@ -255,7 +255,7 @@ static int http1_parse_request_line (http_ctx_t *hc, http_ctx_t *req, u8 *rx_buf, http_status_code_t *ec) { int i, target_len; - u32 next_line_offset, method_offset; + u32 next_line_offset, method_len, method_offset; /* request-line = method SP request-target SP HTTP-version CRLF */ i = http_v_find_index (rx_buf, 8, 0, "\r\n"); @@ -282,28 +282,29 @@ http1_parse_request_line (http_ctx_t *hc, http_ctx_t *req, u8 *rx_buf, http_stat * In the interest of robustness, a server that is expecting to receive and * parse a request-line SHOULD ignore at least one empty line (CRLF) * received prior to the request-line. - */ + */ method_offset = rx_buf[0] == '\r' && rx_buf[1] == '\n' ? 2 : 0; + method_len = i - method_offset; /* parse method */ - if (!memcmp (rx_buf + method_offset, "GET ", 4)) + if (method_len >= 4 && !memcmp (rx_buf + method_offset, "GET ", 4)) { HTTP_DBG (1, "GET method"); req->method = HTTP_REQ_GET; req->target_path_offset = method_offset + 4; } - else if (!memcmp (rx_buf + method_offset, "POST ", 5)) + else if (method_len >= 5 && !memcmp (rx_buf + method_offset, "POST ", 5)) { HTTP_DBG (1, "POST method"); req->method = HTTP_REQ_POST; req->target_path_offset = method_offset + 5; } - else if (!memcmp (rx_buf + method_offset, "PUT ", 4)) + else if (method_len >= 4 && !memcmp (rx_buf + method_offset, "PUT ", 4)) { HTTP_DBG (1, "PUT method"); req->method = HTTP_REQ_PUT; req->target_path_offset = method_offset + 4; } - else if (!memcmp (rx_buf + method_offset, "CONNECT ", 8)) + else if (method_len >= 8 && !memcmp (rx_buf + method_offset, "CONNECT ", 8)) { HTTP_DBG (1, "CONNECT method"); req->method = HTTP_REQ_CONNECT; @@ -311,7 +312,7 @@ http1_parse_request_line (http_ctx_t *hc, http_ctx_t *req, u8 *rx_buf, http_stat req->target_path_offset = method_offset + 8; req->req_flags |= HTTP_REQ_F_IS_TUNNEL; } - else if (!memcmp (rx_buf + method_offset, "CONNECT-UDP ", 12) && + else if (method_len >= 12 && !memcmp (rx_buf + method_offset, "CONNECT-UDP ", 12) && (hc->flags & HTTP_CONN_F_CONNECT_UDP_DRAFT03)) { HTTP_DBG (1, "CONNECT-UDP method"); @@ -321,7 +322,7 @@ http1_parse_request_line (http_ctx_t *hc, http_ctx_t *req, u8 *rx_buf, http_stat req->req_flags |= HTTP_REQ_F_IS_TUNNEL; req->req_flags |= HTTP_REQ_F_CONNECT_UDP_DRAFT03; } - else if (!memcmp (rx_buf + method_offset, "PRI ", 4)) + else if (method_len >= 4 && !memcmp (rx_buf + method_offset, "PRI ", 4)) { HTTP_DBG (1, "request start with PRI, upgrading to http/2"); return 2; diff --git a/src/plugins/http_static/http_static.c b/src/plugins/http_static/http_static.c index 6bd2519f3f11..a7392d9b22f3 100644 --- a/src/plugins/http_static/http_static.c +++ b/src/plugins/http_static/http_static.c @@ -155,22 +155,11 @@ vl_api_http_static_get_t_handler (vl_api_http_static_get_t *mp) { hss_main_t *hsm = &hss_main; vl_api_http_static_get_reply_t *rmp; - u8 *addr = NULL; int rv = 0; - if (hsm->default_listener.sep.is_ip4) - { - addr = format (addr, "%U", format_ip4_address, &hsm->default_listener.sep.ip.ip4); - } - else - { - addr = format (addr, "[%U]", format_ip6_address, &hsm->default_listener.sep.ip.ip6); - } - /* format() does not NULL terminate; snprintf("%s", addr) would - read past the end of the vector without this NULL termination. */ - vec_add1 (addr, 0); - REPLY_MACRO2 (VL_API_HTTP_STATIC_GET_REPLY, { + u8 *addr = 0; + if (pool_elts (hsm->listeners) == 0) { rmp->fifo_size = 0; @@ -184,6 +173,14 @@ vl_api_http_static_get_t_handler (vl_api_http_static_get_t *mp) } else { + if (hsm->default_listener.sep.is_ip4) + addr = format (addr, "%U", format_ip4_address, &hsm->default_listener.sep.ip.ip4); + else + addr = format (addr, "[%U]", format_ip6_address, &hsm->default_listener.sep.ip.ip6); + /* format() does not NULL terminate; snprintf("%s", addr) would + read past the end of the vector without this NULL termination. */ + vec_add1 (addr, 0); + rmp->fifo_size = htonl (hsm->fifo_size); rmp->cache_size_limit = htonl (hsm->default_listener.cache.cache_limit); strncpy ((char *) rmp->www_root, (const char *) hsm->default_listener.www_root, @@ -195,10 +192,9 @@ vl_api_http_static_get_t_handler (vl_api_http_static_get_t *mp) rmp->keepalive_timeout = htonl (hsm->default_listener.keepalive_timeout); rmp->max_body_size = htonl (hsm->default_listener.max_req_body_size); rmp->rx_buff_thresh = htonl (hsm->default_listener.rx_buff_thresh); + vec_free (addr); } }); - - vec_free (addr); } #include diff --git a/src/plugins/http_static/static_server.c b/src/plugins/http_static/static_server.c index 19a0e7bc5ec2..23b7ba8a6314 100644 --- a/src/plugins/http_static/static_server.c +++ b/src/plugins/http_static/static_server.c @@ -1122,6 +1122,9 @@ hss_destroy (vlib_main_t *vm) hss_listener_t *l; int ret; + if (!hsm->is_init) + return 0; + while (pool_elts (hsm->listeners)) { l = pool_elt_at_index (hsm->listeners, pool_get_first_index (hsm->listeners)); diff --git a/src/plugins/ipip/ipip.c b/src/plugins/ipip/ipip.c index 10e231979325..4cbffde93b2f 100644 --- a/src/plugins/ipip/ipip.c +++ b/src/plugins/ipip/ipip.c @@ -440,18 +440,17 @@ mipip_mk_incomplete_walk (adj_index_t ai, void *data) void mipip_update_adj (vnet_main_t * vnm, u32 sw_if_index, adj_index_t ai) { - ipip_main_t *gm = &ipip_main; adj_midchain_fixup_t fixup; ip_adjacency_t *adj; teib_entry_t *ne; ipip_tunnel_t *t; adj_flags_t af; - u32 ti; af = ADJ_FLAG_NONE; adj = adj_get (ai); - ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; - t = pool_elt_at_index (gm->tunnels, ti); + t = ipip_tunnel_db_find_by_sw_if_index (sw_if_index); + if (!t) + return; ne = teib_entry_find_46 (sw_if_index, adj->ia_nh_proto, &adj->sub_type.nbr.next_hop); @@ -584,10 +583,12 @@ ipip_tunnel_t * ipip_tunnel_db_find_by_sw_if_index (u32 sw_if_index) { ipip_main_t *gm = &ipip_main; + u32 ti; + if (vec_len (gm->tunnel_index_by_sw_if_index) <= sw_if_index) return NULL; - u32 ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; - if (ti == ~0) + ti = gm->tunnel_index_by_sw_if_index[sw_if_index]; + if (ti == ~0 || pool_is_free_index (gm->tunnels, ti)) return NULL; return pool_elt_at_index (gm->tunnels, ti); } @@ -655,7 +656,7 @@ ipip_teib_entry_added (const teib_entry_t * ne) u32 t_idx; sw_if_index = teib_entry_get_sw_if_index (ne); - if (vec_len (gm->tunnel_index_by_sw_if_index) < sw_if_index) + if (vec_len (gm->tunnel_index_by_sw_if_index) <= sw_if_index) return; t_idx = gm->tunnel_index_by_sw_if_index[sw_if_index]; @@ -692,7 +693,7 @@ ipip_teib_entry_deleted (const teib_entry_t * ne) u32 t_idx; sw_if_index = teib_entry_get_sw_if_index (ne); - if (vec_len (gm->tunnel_index_by_sw_if_index) < sw_if_index) + if (vec_len (gm->tunnel_index_by_sw_if_index) <= sw_if_index) return; t_idx = gm->tunnel_index_by_sw_if_index[sw_if_index]; @@ -856,6 +857,20 @@ ipip_del_tunnel (u32 sw_if_index) if (t == NULL) return VNET_API_ERROR_NO_SUCH_ENTRY; + /* + * A 6RD tunnel carries IPv6 FIB state that only sixrd_del_tunnel() + * releases, and no caller of this function can ask for that mode: + * ipip_add_tunnel() maps its tunnel_mode_t argument to IPIP_MODE_P2P or + * IPIP_MODE_P2MP, and a 6RD tunnel is created by sixrd_add_tunnel() and + * deleted by sixrd_del_tunnel(). + */ + if (IPIP_MODE_6RD == t->mode) + { + clib_warning ("IPIP tunnel delete: tunnel is a 6RD tunnel: %d", + sw_if_index); + return VNET_API_ERROR_INVALID_VALUE; + } + if (t->mode == IPIP_MODE_P2MP) teib_walk_itf (t->sw_if_index, ipip_tunnel_delete_teib_walk, t); diff --git a/src/plugins/ipip/sixrd.c b/src/plugins/ipip/sixrd.c index 646c0dd3f0bb..a0fa49cad4c9 100644 --- a/src/plugins/ipip/sixrd.c +++ b/src/plugins/ipip/sixrd.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -371,6 +372,12 @@ sixrd_del_tunnel (u32 sw_if_index) return -1; } + if (IPIP_MODE_6RD != t->mode) + { + clib_warning ("SIXRD tunnel delete: not a 6RD tunnel: %d", sw_if_index); + return VNET_API_ERROR_INVALID_VALUE; + } + fib_prefix_t pfx6 = { .fp_proto = FIB_PROTOCOL_IP6, .fp_len = t->sixrd.ip6_prefix_len, diff --git a/src/plugins/linux-cp/lcp.c b/src/plugins/linux-cp/lcp.c index 712969cba8db..016dadeb32d3 100644 --- a/src/plugins/linux-cp/lcp.c +++ b/src/plugins/linux-cp/lcp.c @@ -16,7 +16,9 @@ #include #include -lcp_main_t lcp_main; +lcp_main_t lcp_main = { + .default_ns_fd = -1, +}; u8 * lcp_get_default_ns (void) @@ -56,9 +58,9 @@ lcp_set_default_ns (u8 *ns) if (!p || *p == 0) { lcpm->default_namespace = NULL; - if (lcpm->default_ns_fd > 0) + if (lcpm->default_ns_fd >= 0) close (lcpm->default_ns_fd); - lcpm->default_ns_fd = 0; + lcpm->default_ns_fd = -1; return 0; } diff --git a/src/plugins/linux-cp/lcp_nl.c b/src/plugins/linux-cp/lcp_nl.c index 56cdd5ec17c3..9da9f2376bb7 100644 --- a/src/plugins/linux-cp/lcp_nl.c +++ b/src/plugins/linux-cp/lcp_nl.c @@ -181,9 +181,9 @@ nl_register_vft (const nl_vft_t *nv) #define NL_INFO(...) vlib_log_notice (nl_main.nl_logger, __VA_ARGS__); #define NL_ERROR(...) vlib_log_err (nl_main.nl_logger, __VA_ARGS__); -static void lcp_nl_open_socket (void); +static int lcp_nl_open_socket (void); static void lcp_nl_close_socket (void); -static void lcp_nl_open_sync_socket (nl_sock_type_t sock_type); +static int lcp_nl_open_sync_socket (nl_sock_type_t sock_type); static void lcp_nl_close_sync_socket (nl_sock_type_t sock_type); static void @@ -395,6 +395,9 @@ lcp_nl_route_send_dump_req (nl_sock_type_t sock_type, int msg_type) .rtgen_family = AF_UNSPEC, }; + if (!sk_route) + return -NLE_BAD_SOCK; + err = nl_send_simple (sk_route, msg_type, NLM_F_DUMP, &rt_hdr, sizeof (rt_hdr)); @@ -415,7 +418,10 @@ lcp_nl_route_dump_cb (struct nl_msg *msg, void *arg) int err; if ((err = nl_msg_parse (msg, nl_route_dispatch, NULL)) < 0) - NL_ERROR ("Unable to parse object: %s", nl_geterror (err)); + { + NL_ERROR ("Unable to parse object: %s", nl_geterror (err)); + return err; + } return NL_OK; } @@ -435,6 +441,9 @@ lcp_nl_recv_dump_replies (nl_sock_type_t sock_type, int msg_limit, int done = 0; int n_msgs = 0; + if (!sk_route) + return -NLE_BAD_SOCK; + lcp_set_netlink_processing_active (1); continue_reading: @@ -505,12 +514,20 @@ lcp_nl_recv_dump_replies (nl_sock_type_t sock_type, int msg_limit, */ else { - lcp_nl_route_dump_cb (msg, NULL); + err = lcp_nl_route_dump_cb (msg, NULL); + if (err < 0) + goto out; } hdr = nlmsg_next (hdr, &n_bytes); } + if (n_bytes != 0) + { + err = -NLE_MSG_TRUNC; + goto out; + } + nlmsg_free (msg); free (buf); msg = NULL; @@ -604,7 +621,9 @@ nl_route_process (vlib_main_t *vm, vlib_node_runtime_t *node, * If send a dump request while the previous one is in progress, * the request will fail and EBUSY returned */ -#define _(stype, mtype, tname, fn) lcp_nl_open_sync_socket (stype); +#define _(stype, mtype, tname, fn) \ + if (lcp_nl_open_sync_socket (stype) < 0) \ + goto sync_later; foreach_sock_type #undef _ @@ -614,19 +633,25 @@ nl_route_process (vlib_main_t *vm, vlib_node_runtime_t *node, * the moment. Once all the dump replies are processed, the * notifications will be processed */ - lcp_nl_open_socket (); + if (lcp_nl_open_socket () < 0) + goto sync_later; /* Request the current entry set from the kernel for every data type * of interest. Thus requesting a snapshot of the current routing * state that the kernel will make and then reply with */ -#define _(stype, mtype, tname, fn) lcp_nl_route_send_dump_req (stype, mtype); +#define _(stype, mtype, tname, fn) \ + if (lcp_nl_route_send_dump_req (stype, mtype) < 0) \ + goto sync_later; foreach_sock_type #undef _ +#define _(stype, mtype, tname, fn) nl_##fn##_sync_begin (); + foreach_sock_type +#undef _ + /* Process all the dump replies */ #define _(stype, mtype, tname, fn) \ - nl_##fn##_sync_begin (); \ is_done = 0; \ do \ { \ @@ -637,12 +662,12 @@ nl_route_process (vlib_main_t *vm, vlib_node_runtime_t *node, NL_ERROR ("Error receiving dump replies of type " tname \ ": %s (%d)", \ nl_geterror (n_msgs), n_msgs); \ - break; \ + goto sync_later; \ } \ else if (n_msgs == 0) \ { \ NL_ERROR ("EOF while receiving dump replies of type " tname); \ - break; \ + goto sync_later; \ } \ else \ NL_INFO ("Processed %u dump replies of type " tname, n_msgs); \ @@ -655,7 +680,7 @@ nl_route_process (vlib_main_t *vm, vlib_node_runtime_t *node, * incomplete \ */ \ vlib_process_wait_for_event_or_clock (vm, \ - nm->sync_batch_delay_ms * 1e-3); \ + nm->sync_batch_delay_ms * 1e-3); \ event_type = vlib_process_get_events (vm, &event_data); \ vec_reset_length (event_data); \ \ @@ -665,14 +690,17 @@ nl_route_process (vlib_main_t *vm, vlib_node_runtime_t *node, if (event_type == NL_EVENT_ERR) \ goto sync_later; \ } \ - while (!is_done); \ - nl_##fn##_sync_end (); + while (!is_done); - foreach_sock_type + foreach_sock_type #undef _ - /* Start processing notifications */ - nm->nl_status = NL_STATUS_NOTIF_PROC; +#define _(stype, mtype, tname, fn) nl_##fn##_sync_end (); + foreach_sock_type +#undef _ + + /* Start processing notifications */ + nm->nl_status = NL_STATUS_NOTIF_PROC; /* Trigger messages processing if there are notifications received * during synchronization @@ -751,7 +779,12 @@ lcp_nl_pair_add_cb (lcp_itf_pair_t *pair) if (!nm->sk_route) { NL_INFO ("pair_add_cb: Opening netlink socket, LCP pairs %u", lcp_itf_num_pairs ()); - lcp_nl_open_socket (); + if (lcp_nl_open_socket () < 0) + { + nm->nl_status = NL_STATUS_SYNC; + vlib_process_signal_event (vlib_get_main (), nl_route_process_node.index, + NL_EVENT_ERR, 0); + } } else if (!lcp_get_netlink_processing_active ()) lcp_nl_drain_messages (); @@ -886,11 +919,13 @@ lcp_nl_close_socket (void) } } -static void +static int lcp_nl_open_socket (void) { nl_main_t *nm = &nl_main; - int dest_ns_fd, curr_ns_fd; + int dest_ns_fd, curr_ns_fd = -1; + int entered_dest_ns = 0; + int err; /* Allocate a new socket for both routes and acls * Notifications do not use sequence numbers, disable sequence number @@ -899,31 +934,56 @@ lcp_nl_open_socket (void) * received */ nm->sk_route = nl_socket_alloc (); + if (!nm->sk_route) + return -NLE_NOMEM; nl_socket_disable_seq_check (nm->sk_route); dest_ns_fd = lcp_get_default_ns_fd (); - if (dest_ns_fd) + if (dest_ns_fd >= 0) { - curr_ns_fd = open ("/proc/self/ns/net", O_RDONLY); - setns (dest_ns_fd, CLONE_NEWNET); + curr_ns_fd = clib_netns_open (NULL /* self */); + if (curr_ns_fd < 0) + { + err = -NLE_FAILURE; + goto error; + } + if (clib_setns (dest_ns_fd) < 0) + { + err = -NLE_FAILURE; + goto error; + } + entered_dest_ns = 1; } - nl_connect (nm->sk_route, NETLINK_ROUTE); + err = nl_connect (nm->sk_route, NETLINK_ROUTE); + if (err < 0) + goto error; - if (dest_ns_fd && curr_ns_fd >= 0) + if (entered_dest_ns) + { + if (clib_setns (curr_ns_fd) < 0) + { + err = -NLE_FAILURE; + goto error; + } + entered_dest_ns = 0; + } + if (curr_ns_fd >= 0) { - setns (curr_ns_fd, CLONE_NEWNET); close (curr_ns_fd); + curr_ns_fd = -1; } /* Subscribe to all the 'routing' notifications on the route socket */ - nl_socket_add_memberships (nm->sk_route, RTNLGRP_LINK, RTNLGRP_IPV6_IFADDR, - RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE, - RTNLGRP_IPV6_ROUTE, RTNLGRP_NEIGH, RTNLGRP_NOTIFY, + err = nl_socket_add_memberships (nm->sk_route, RTNLGRP_LINK, RTNLGRP_IPV6_IFADDR, + RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, + RTNLGRP_NEIGH, RTNLGRP_NOTIFY, #ifdef RTNLGRP_MPLS_ROUTE /* not defined on CentOS/RHEL 7 */ - RTNLGRP_MPLS_ROUTE, + RTNLGRP_MPLS_ROUTE, #endif - RTNLGRP_IPV4_RULE, RTNLGRP_IPV6_RULE, 0); + RTNLGRP_IPV4_RULE, RTNLGRP_IPV6_RULE, 0); + if (err < 0) + goto error; /* Set socket in nonblocking mode and increase buffer sizes */ nl_socket_set_nonblocking (nm->sk_route); @@ -955,13 +1015,38 @@ lcp_nl_open_socket (void) nl_socket_modify_cb (nm->sk_route, NL_CB_VALID, NL_CB_CUSTOM, nl_route_cb, NULL); NL_INFO ("Opened netlink socket %d", nl_socket_get_fd (nm->sk_route)); + return 0; + +error: + if (entered_dest_ns) + { + if (clib_setns (curr_ns_fd) < 0) + { + NL_ERROR ("Cannot set previous ns"); + } + else + { + entered_dest_ns = 0; + } + } + if (curr_ns_fd >= 0) + { + close (curr_ns_fd); + curr_ns_fd = -1; + } + NL_ERROR ("Unable to open netlink socket: %s", nl_geterror (err)); + nl_socket_free (nm->sk_route); + nm->sk_route = NULL; + return err; } -static void +static int lcp_nl_open_sync_socket (nl_sock_type_t sock_type) { nl_main_t *nm = &nl_main; - int dest_ns_fd, curr_ns_fd; + int dest_ns_fd, curr_ns_fd = -1; + int entered_dest_ns = 0; + int err; struct nl_sock *sk_route; /* Allocate a new blocking socket for routes that will be used for dump @@ -971,33 +1056,71 @@ lcp_nl_open_sync_socket (nl_sock_type_t sock_type) */ nm->sk_route_sync[sock_type] = sk_route = nl_socket_alloc (); + if (!sk_route) + return -NLE_NOMEM; dest_ns_fd = lcp_get_default_ns_fd (); - if (dest_ns_fd > 0) + if (dest_ns_fd >= 0) { curr_ns_fd = clib_netns_open (NULL /* self */); - if (clib_setns (dest_ns_fd) == -1) - NL_ERROR ("Cannot set destination ns"); + if (curr_ns_fd < 0) + { + err = -NLE_FAILURE; + goto error; + } + if (clib_setns (dest_ns_fd) < 0) + { + err = -NLE_FAILURE; + goto error; + } + entered_dest_ns = 1; } - nl_connect (sk_route, NETLINK_ROUTE); + err = nl_connect (sk_route, NETLINK_ROUTE); + if (err < 0) + goto error; - if (dest_ns_fd > 0) + if (entered_dest_ns) { - if (curr_ns_fd == -1) + if (clib_setns (curr_ns_fd) < 0) { - NL_ERROR ("No previous ns to set"); - } - else - { - if (clib_setns (curr_ns_fd) == -1) - NL_ERROR ("Cannot set previous ns"); - close (curr_ns_fd); + err = -NLE_FAILURE; + goto error; } + entered_dest_ns = 0; + } + if (curr_ns_fd >= 0) + { + close (curr_ns_fd); + curr_ns_fd = -1; } NL_INFO ("Opened netlink synchronization socket %d of type %d", nl_socket_get_fd (sk_route), sock_type); + return 0; + +error: + if (entered_dest_ns) + { + if (clib_setns (curr_ns_fd) < 0) + { + NL_ERROR ("Cannot set previous ns"); + } + else + { + entered_dest_ns = 0; + } + } + if (curr_ns_fd >= 0) + { + close (curr_ns_fd); + curr_ns_fd = -1; + } + nl_socket_free (sk_route); + nm->sk_route_sync[sock_type] = NULL; + NL_ERROR ("Unable to open netlink synchronization socket of type %d: %s", sock_type, + nl_geterror (err)); + return err; } static void diff --git a/src/plugins/linux-cp/lcp_router.c b/src/plugins/linux-cp/lcp_router.c index 32c886ca94d9..ad10906bb521 100644 --- a/src/plugins/linux-cp/lcp_router.c +++ b/src/plugins/linux-cp/lcp_router.c @@ -85,26 +85,15 @@ static const mfib_prefix_t ip6_specials[] = { }, }; -/* VIF to PHY DB of managed interfaces */ -static uword *lcp_routing_itf_db; - static u32 lcp_router_intf_h2p (u32 host) { lcp_itf_pair_t *lip; index_t lipi; - uword *p; - - /* - * first check the linux side created interface (i.e. vlans, tunnels etc) - */ - p = hash_get (lcp_routing_itf_db, host); - - if (p) - return p[0]; /* - * then check the paired phys + * Find the PHY interface paired with the Linux VIF named in the netlink + * message. */ lipi = lcp_itf_pair_find_by_vif (host); @@ -1128,6 +1117,22 @@ lcp_router_route_path_parse (struct rtnl_nexthop *rnh, void *arg) sw_if_index = lcp_router_intf_h2p (rtnl_route_nh_get_ifindex (rnh)); + /* + * The pair lookup is a hash lookup plus a pool index; it does not tell us + * whether the interface still exists. A route notification can name an + * interface that has already been removed, which happens routinely while + * the netlink state is being replayed after a socket error: the link dump + * deletes LCP pairs before the route dump is read. Dropping the nexthop + * here keeps a stale sw_if_index out of the FIB, where it would later be + * turned into an out-of-range fib_index. + */ + if (~0 != sw_if_index && + NULL == vnet_get_sw_interface_or_null (vnet_get_main (), sw_if_index)) + { + LCP_ROUTER_DBG (" skipping nexthop on dead sw_if_index %u", sw_if_index); + sw_if_index = ~0; + } + if (~0 != sw_if_index) { fib_protocol_t fproto; diff --git a/src/plugins/memif/device.c b/src/plugins/memif/device.c index 367728ec8f40..54e6a2848d0b 100644 --- a/src/plugins/memif/device.c +++ b/src/plugins/memif/device.c @@ -17,7 +17,8 @@ #define foreach_memif_tx_func_error \ _ (NO_FREE_SLOTS, no_free_slots, ERROR, "no free tx slots") \ - _ (ROLLBACK, rollback, ERROR, "no enough space in tx buffers") + _ (ROLLBACK, rollback, ERROR, "no enough space in tx buffers") \ + _ (BAD_RING, bad_ring, ERROR, "invalid ring metadata") typedef enum { @@ -308,7 +309,7 @@ memif_interface_tx_zc_inline (vlib_main_t *vm, vlib_node_runtime_t *node, memif_per_thread_data_t *ptd, u32 n_left) { memif_ring_t *ring = mq->ring; - u16 slot, free_slots, n_free; + u16 slot, free_slots, n_free, n_used; u16 ring_size = 1 << mq->log2_ring_size; u16 mask = ring_size - 1; int n_retries = 5; @@ -322,6 +323,13 @@ memif_interface_tx_zc_inline (vlib_main_t *vm, vlib_node_runtime_t *node, slot = head = ring->head; n_free = tail - mq->last_tail; + n_used = head - mq->last_tail; + if (PREDICT_FALSE (n_used > ring_size || n_free > n_used)) + { + vlib_error_count (vm, node->node_index, MEMIF_TX_ERROR_BAD_RING, 1); + return n_left; + } + if (n_free >= 16) { vlib_buffer_free_from_ring_no_next (vm, mq->buffers, diff --git a/src/plugins/memif/node.c b/src/plugins/memif/node.c index 46f51c5ee0d6..35e3b2735a32 100644 --- a/src/plugins/memif/node.c +++ b/src/plugins/memif/node.c @@ -220,6 +220,29 @@ memif_validate_desc_data (memif_per_thread_data_t *ptd, memif_if_t *mif, ptd->n_rx_bytes = n_rx_bytes; } +static_always_inline int +memif_zc_rx_packet_valid (memif_ring_t *ring, u16 cur_slot, u16 n_slots, + u16 mask, u32 buffer_length, u16 *packet_slots) +{ + int valid = 1; + + *packet_slots = 0; + while (*packet_slots < n_slots) + { + memif_desc_t *d = &ring->desc[(cur_slot + *packet_slots) & mask]; + + (*packet_slots)++; + if (d->length == 0 || d->length > buffer_length || + (d->flags & ~MEMIF_DESC_FLAG_NEXT)) + valid = 0; + + if ((d->flags & MEMIF_DESC_FLAG_NEXT) == 0) + return valid; + } + + return 0; +} + static_always_inline u32 memif_process_desc (vlib_main_t *vm, vlib_node_runtime_t *node, memif_per_thread_data_t *ptd, memif_if_t *mif) @@ -784,12 +807,48 @@ memif_device_input_zc_inline (vlib_main_t *vm, vlib_node_runtime_t *node, goto refill; n_slots = last_slot - cur_slot; + if (PREDICT_FALSE (n_slots > ring_size)) + { + vlib_error_count (vm, node->node_index, MEMIF_INPUT_ERROR_BAD_DESC, 1); + goto done; + } + + /* ring->tail is written by the peer: only the slots that were published to + * it hold a valid buffer index in mq->buffers[] */ + head = __atomic_load_n (&ring->head, __ATOMIC_ACQUIRE); + if (PREDICT_FALSE (n_slots > (u16) (head - cur_slot))) + { + vlib_error_count (vm, node->node_index, MEMIF_INPUT_ERROR_BAD_DESC, 1); + n_slots = head - cur_slot; + if (n_slots == 0) + goto refill; + } + /* process ring slots */ vec_validate_aligned (ptd->buffers, MEMIF_RX_VECTOR_SZ, CLIB_CACHE_LINE_BYTES); while (n_slots && n_rx_packets < MEMIF_RX_VECTOR_SZ) { vlib_buffer_t *hb; + u16 packet_slots; + + if (PREDICT_FALSE (!memif_zc_rx_packet_valid ( + ring, cur_slot, n_slots, mask, buffer_length, &packet_slots))) + { + u16 slot_i; + + /* These descriptors never reach the graph and the refill below + overwrites the ring's copy of their buffer indices, so return the + buffers here. */ + for (slot_i = 0; slot_i < packet_slots; slot_i++) + vlib_buffer_free_one (vm, mq->buffers[(cur_slot + slot_i) & mask]); + + cur_slot += packet_slots; + n_slots -= packet_slots; + vlib_error_count (vm, node->node_index, MEMIF_INPUT_ERROR_BAD_DESC, + 1); + continue; + } s0 = cur_slot & mask; bi0 = mq->buffers[s0]; diff --git a/src/plugins/nat/nat44-ed/nat44_ed.c b/src/plugins/nat/nat44-ed/nat44_ed.c index 3c3c51fea3a4..1d1f27051512 100644 --- a/src/plugins/nat/nat44-ed/nat44_ed.c +++ b/src/plugins/nat/nat44-ed/nat44_ed.c @@ -3082,7 +3082,8 @@ nat44_ed_get_out2in_worker_index (vlib_buffer_t *b, ip4_header_t *ip, snat_main_t *sm = &snat_main; clib_bihash_kv_16_8_t kv16, value16; - u8 proto, next_worker_index = 0; + u8 proto; + u32 next_worker_index = 0; u16 port; snat_static_mapping_t *m; u32 hash; diff --git a/src/plugins/nat/nat64/nat64_db.c b/src/plugins/nat/nat64/nat64_db.c index 61352f7457de..688fac3e3d88 100644 --- a/src/plugins/nat/nat64/nat64_db.c +++ b/src/plugins/nat/nat64/nat64_db.c @@ -122,9 +122,10 @@ nat64_db_bib_entry_create (u32 thread_index, nat64_db_t * db, kv.key[2] = bibe_key.as_u64[2]; clib_bihash_add_del_24_8 (&db->bib.out2in, &kv, 1); - fib_table_t *fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); - nat_ipfix_logging_nat64_bib (thread_index, in_addr, out_addr, proto, - in_port, out_port, fib->ft_table_id, 1); + fib_table_t *fib = fib_table_get_or_null (bibe->fib_index, FIB_PROTOCOL_IP6); + if (fib) + nat_ipfix_logging_nat64_bib (thread_index, in_addr, out_addr, proto, in_port, out_port, + fib->ft_table_id, 1); return bibe; } @@ -195,10 +196,10 @@ nat64_db_bib_entry_free (u32 thread_index, nat64_db_t * db, if (!db->addr_free) db->free_addr_port_cb (db, &bibe->out_addr, bibe->out_port, bibe->proto); - fib_table_t *fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); - nat_ipfix_logging_nat64_bib (thread_index, &bibe->in_addr, &bibe->out_addr, - bibe->proto, bibe->in_port, bibe->out_port, - fib->ft_table_id, 0); + fib_table_t *fib = fib_table_get_or_null (bibe->fib_index, FIB_PROTOCOL_IP6); + if (fib) + nat_ipfix_logging_nat64_bib (thread_index, &bibe->in_addr, &bibe->out_addr, bibe->proto, + bibe->in_port, bibe->out_port, fib->ft_table_id, 0); /* delete from pool */ pool_put (bib, bibe); @@ -435,16 +436,13 @@ nat64_db_st_entry_create (u32 thread_index, nat64_db_t * db, kv.key[5] = ste_key.as_u64[5]; clib_bihash_add_del_48_8 (&db->st.out2in, &kv, 1); - fib_table_t *fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); - nat_ipfix_logging_nat64_session (thread_index, &bibe->in_addr, - &bibe->out_addr, bibe->proto, - bibe->in_port, bibe->out_port, - &ste->in_r_addr, &ste->out_r_addr, - ste->r_port, ste->r_port, fib->ft_table_id, - 1); - nat_syslog_nat64_sadd (bibe->fib_index, &bibe->in_addr, bibe->in_port, - &bibe->out_addr, bibe->out_port, &ste->out_r_addr, - ste->r_port, bibe->proto); + fib_table_t *fib = fib_table_get_or_null (bibe->fib_index, FIB_PROTOCOL_IP6); + if (fib) + nat_ipfix_logging_nat64_session ( + thread_index, &bibe->in_addr, &bibe->out_addr, bibe->proto, bibe->in_port, bibe->out_port, + &ste->in_r_addr, &ste->out_r_addr, ste->r_port, ste->r_port, fib->ft_table_id, 1); + nat_syslog_nat64_sadd (bibe->fib_index, &bibe->in_addr, bibe->in_port, &bibe->out_addr, + bibe->out_port, &ste->out_r_addr, ste->r_port, bibe->proto); return ste; } @@ -508,16 +506,13 @@ nat64_db_st_entry_free (u32 thread_index, kv.key[5] = ste_key.as_u64[5]; clib_bihash_add_del_48_8 (&db->st.out2in, &kv, 0); - fib_table_t *fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); - nat_ipfix_logging_nat64_session (thread_index, &bibe->in_addr, - &bibe->out_addr, bibe->proto, - bibe->in_port, bibe->out_port, - &ste->in_r_addr, &ste->out_r_addr, - ste->r_port, ste->r_port, fib->ft_table_id, - 0); - nat_syslog_nat64_sdel (bibe->fib_index, &bibe->in_addr, bibe->in_port, - &bibe->out_addr, bibe->out_port, &ste->out_r_addr, - ste->r_port, bibe->proto); + fib_table_t *fib = fib_table_get_or_null (bibe->fib_index, FIB_PROTOCOL_IP6); + if (fib) + nat_ipfix_logging_nat64_session ( + thread_index, &bibe->in_addr, &bibe->out_addr, bibe->proto, bibe->in_port, bibe->out_port, + &ste->in_r_addr, &ste->out_r_addr, ste->r_port, ste->r_port, fib->ft_table_id, 0); + nat_syslog_nat64_sdel (bibe->fib_index, &bibe->in_addr, bibe->in_port, &bibe->out_addr, + bibe->out_port, &ste->out_r_addr, ste->r_port, bibe->proto); /* delete from pool */ pool_put (st, ste); diff --git a/src/plugins/pppoe/pppoe.c b/src/plugins/pppoe/pppoe.c index 0f5c395ecc46..20c244d839cb 100644 --- a/src/plugins/pppoe/pppoe.c +++ b/src/plugins/pppoe/pppoe.c @@ -79,21 +79,43 @@ format_pppoe_header_with_length (u8 * s, va_list * args) return s; } +/* + * Find the session that owns a sw_if_index. The mapping is indexed with + * the interface the caller was handed, which for a sub-interface is the + * sub-interface's own index, so it can be one past the end of the mapping + * and the slot it names can be free or hold the invalid sentinel. + */ +static pppoe_session_t * +pppoe_session_find_by_sw_if_index (u32 sw_if_index) +{ + pppoe_main_t *pem = &pppoe_main; + u32 session_id; + + if (vec_len (pem->session_index_by_sw_if_index) <= sw_if_index) + return NULL; + + session_id = pem->session_index_by_sw_if_index[sw_if_index]; + + if (session_id == ~0 || pool_is_free_index (pem->sessions, session_id)) + return NULL; + + return pool_elt_at_index (pem->sessions, session_id); +} + static u8 * pppoe_build_rewrite (vnet_main_t * vnm, u32 sw_if_index, vnet_link_t link_type, const void *dst_address) { - pppoe_main_t *pem = &pppoe_main; pppoe_session_t *t; vnet_hw_interface_t *hi; vnet_sw_interface_t *si; pppoe_header_t *pppoe; - u32 session_id; u8 *rw = 0; - session_id = pem->session_index_by_sw_if_index[sw_if_index]; - t = pool_elt_at_index (pem->sessions, session_id); + t = pppoe_session_find_by_sw_if_index (sw_if_index); + if (!t) + return 0; int len = sizeof (pppoe_header_t) + sizeof (ethernet_header_t); si = vnet_get_sw_interface (vnm, t->encap_if_index); @@ -173,18 +195,17 @@ pppoe_fixup (vlib_main_t * vm, static void pppoe_update_adj (vnet_main_t * vnm, u32 sw_if_index, adj_index_t ai) { - pppoe_main_t *pem = &pppoe_main; dpo_id_t dpo = DPO_INVALID; ip_adjacency_t *adj; pppoe_session_t *t; vnet_sw_interface_t *si; - u32 session_id; ASSERT (ADJ_INDEX_INVALID != ai); adj = adj_get (ai); - session_id = pem->session_index_by_sw_if_index[sw_if_index]; - t = pool_elt_at_index (pem->sessions, session_id); + t = pppoe_session_find_by_sw_if_index (sw_if_index); + if (!t) + return; uword len = sizeof (ethernet_header_t); diff --git a/src/plugins/rdma/cli.c b/src/plugins/rdma/cli.c index a9f363162aec..4190eb699465 100644 --- a/src/plugins/rdma/cli.c +++ b/src/plugins/rdma/cli.c @@ -18,11 +18,15 @@ static clib_error_t * rdma_create_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) { - rdma_create_if_args_t args; + rdma_create_if_args_t args = { }; if (!unformat_user (input, unformat_rdma_create_if_args, &args)) - return clib_error_return (0, "unknown input `%U'", - format_unformat_error, input); + { + vec_free (args.ifname); + vec_free (args.name); + return clib_error_return (0, "unknown input `%U'", + format_unformat_error, input); + } rdma_create_if (vm, &args); @@ -66,8 +70,12 @@ rdma_delete_command_fn (vlib_main_t * vm, unformat_input_t * input, vnm, &sw_if_index)) ; else - return clib_error_return (0, "unknown input `%U'", - format_unformat_error, input); + { + clib_error_t *error = clib_error_return ( + 0, "unknown input `%U'", format_unformat_error, input); + unformat_free (line_input); + return error; + } } unformat_free (line_input); @@ -117,8 +125,12 @@ test_rdma_dump_command_fn (vlib_main_t * vm, unformat_input_t * input, vnm, &sw_if_index)) ; else - return clib_error_return (0, "unknown input `%U'", - format_unformat_error, input); + { + clib_error_t *error = clib_error_return ( + 0, "unknown input `%U'", format_unformat_error, input); + unformat_free (line_input); + return error; + } } unformat_free (line_input); diff --git a/src/plugins/rdma/device.c b/src/plugins/rdma/device.c index 23b16b5c6a1f..6dd2dca592e8 100644 --- a/src/plugins/rdma/device.c +++ b/src/plugins/rdma/device.c @@ -561,6 +561,26 @@ rdma_unregister_interface (vnet_main_t *vnm, rdma_device_t *rd) ethernet_delete_interface (vnm, rd->hw_if_index); } +static void +rdma_rxq_free_buffers (vlib_main_t *vm, rdma_rxq_t *rxq) +{ + u32 owner_tail = clib_max (rxq->tail, rxq->incomplete_tail); + u32 n_owned = owner_tail - rxq->head; + + if (rxq->bufs && n_owned <= rxq->size) + vlib_buffer_free_from_ring (vm, rxq->bufs, rxq->head & (rxq->size - 1), + rxq->size, n_owned); + + if (rxq->second_bufs) + for (u32 i = 0; i < rxq->size; i++) + if (rxq->n_used_per_chain[i] < rxq->n_ds_per_wqe - 1) + vlib_buffer_free (vm, rxq->second_bufs + i, 1); + + vec_free (rxq->bufs); + vec_free (rxq->second_bufs); + vec_free (rxq->n_used_per_chain); +} + static void rdma_txq_free_pending_buffers (vlib_main_t *vm, rdma_txq_t *txq) { @@ -613,6 +633,7 @@ rdma_dev_cleanup (vlib_main_t *vm, rdma_device_t *rd) } } _(ibv_destroy_cq, txq->cq); + vec_free (txq->bufs); } vec_foreach (rxq, rd->rxqs) { @@ -622,6 +643,8 @@ rdma_dev_cleanup (vlib_main_t *vm, rdma_device_t *rd) _(ibv_destroy_rwq_ind_table, rd->rx_rwq_ind_tbl); _(ibv_destroy_qp, rd->rx_qp6); _(ibv_destroy_qp, rd->rx_qp4); + vec_foreach (rxq, rd->rxqs) + rdma_rxq_free_buffers (vm, rxq); _ (ibv_dereg_mr, rd->mr); _(ibv_dealloc_pd, rd->pd); _(ibv_close_device, rd->ctx); @@ -632,6 +655,7 @@ rdma_dev_cleanup (vlib_main_t *vm, rdma_device_t *rd) vec_free (rd->rxqs); vec_free (rd->txqs); vec_free (rd->name); + vec_free (rd->linux_ifname); vlib_pci_free_device_info (rd->pci); pool_put (rm->devices, rd); } @@ -901,7 +925,11 @@ rdma_rxq_finalize (vlib_main_t *vm, rdma_device_t *rd) rwqia.log_ind_tbl_size = min_log2 (vec_len (ind_tbl)); rwqia.ind_tbl = ind_tbl; if ((rd->rx_rwq_ind_tbl = ibv_create_rwq_ind_table (rd->ctx, &rwqia)) == 0) - return clib_error_return_unix (0, "RWQ indirection table create failed"); + { + vec_free (ind_tbl); + return clib_error_return_unix (0, + "RWQ indirection table create failed"); + } vec_free (ind_tbl); memset (&qpia, 0, sizeof (qpia)); @@ -1156,12 +1184,17 @@ rdma_create_if (vlib_main_t * vm, rdma_create_if_args_t * args) } dev_list = ibv_get_device_list (&n_devs); + if (!dev_list) + { + args->error = clib_error_return_unix (0, "failed to get RDMA device list"); + goto err0; + } if (n_devs == 0) { args->error = clib_error_return_unix (0, "no RDMA devices available. Is the ib_uverbs module loaded?"); - goto err0; + goto err1; } /* get PCI address */ @@ -1236,6 +1269,12 @@ rdma_create_if (vlib_main_t * vm, rdma_create_if_args_t * args) goto err2; } + if (!rd->ctx) + { + args->error = clib_error_return_unix (0, "failed to open RDMA device"); + goto err2; + } + if (args->mode != RDMA_MODE_IBV) { struct mlx5dv_context mlx5dv_attrs = { }; @@ -1308,6 +1347,7 @@ are explicitly disabled, and if the interface supports it.*/ rd->rxqs[qid].queue_index = queue_index; } vnet_hw_if_update_runtime_data (vnm, rd->hw_if_index); + ibv_free_device_list (dev_list); vec_free (s); return; diff --git a/src/plugins/rdma/input.c b/src/plugins/rdma/input.c index 259e69c4c9e8..8d87a4e2ff8d 100644 --- a/src/plugins/rdma/input.c +++ b/src/plugins/rdma/input.c @@ -679,15 +679,21 @@ rdma_device_poll_cq_mlx5dv (rdma_device_t * rd, rdma_rxq_t * rxq, /* partially processed mini-cqe array */ u32 n_mini_cqes = rxq->n_mini_cqes; u32 n_mini_cqes_left = rxq->n_mini_cqes_left; + u32 n_process = clib_min (n_mini_cqes_left, VLIB_FRAME_SIZE); + process_mini_cqes (rxq, n_mini_cqes - n_mini_cqes_left, - n_mini_cqes_left, cq_ci, mask, byte_cnt); + n_process, cq_ci, mask, byte_cnt); + clib_memset_u16 (cqe_flags, rxq->last_cqe_flags, n_process); + n_rx_packets = n_process; + byte_cnt += n_process; + cqe_flags += n_process; + rxq->n_mini_cqes_left -= n_process; + + if (rxq->n_mini_cqes_left) + return n_rx_packets; + compressed_cqe_reset_owner (rxq, n_mini_cqes, cq_ci, mask, log2_cq_size); - clib_memset_u16 (cqe_flags, rxq->last_cqe_flags, n_mini_cqes_left); - n_rx_packets = n_mini_cqes_left; - byte_cnt += n_mini_cqes_left; - cqe_flags += n_mini_cqes_left; - rxq->n_mini_cqes_left = 0; rxq->cq_ci = cq_ci = cq_ci + n_mini_cqes; } diff --git a/src/plugins/rdma/unformat.c b/src/plugins/rdma/unformat.c index ffd56295c06b..320de05e1629 100644 --- a/src/plugins/rdma/unformat.c +++ b/src/plugins/rdma/unformat.c @@ -73,5 +73,10 @@ unformat_rdma_create_if_args (unformat_input_t * input, va_list * vargs) } unformat_free (line_input); + if (!ret) + { + vec_free (args->ifname); + vec_free (args->name); + } return ret; } diff --git a/src/plugins/snort/snort.h b/src/plugins/snort/snort.h index 3ffdecbc1c50..584701f5653e 100644 --- a/src/plugins/snort/snort.h +++ b/src/plugins/snort/snort.h @@ -278,12 +278,12 @@ snort_qpair_empty_buf_alloc_buffers (vlib_main_t *vm, snort_qpair_t *qp) daq_vpp_empty_buf_desc_t *empty_buf_desc = &qp->empty_buf_ring[head & mask]; vlib_buffer_t *b = vlib_get_buffer (vm, qp->empty_buffers[head & mask]); + u8 buffer_pool = b->buffer_pool_index; b->current_data = 0; *empty_buf_desc = (daq_vpp_empty_buf_desc_t){ - .buffer_pool = b->buffer_pool_index, + .buffer_pool = buffer_pool, .length = vlib_buffer_get_default_data_size (vm), - .offset = (u8 *) b->data - - sm->buffer_pool_base_addrs[empty_buf_desc->buffer_pool], + .offset = (u8 *) b->data - sm->buffer_pool_base_addrs[buffer_pool], }; head++; i++; diff --git a/src/plugins/srtp/srtp.c b/src/plugins/srtp/srtp.c index f388591d5ea3..b260beb04575 100644 --- a/src/plugins/srtp/srtp.c +++ b/src/plugins/srtp/srtp.c @@ -482,8 +482,13 @@ srtp_migrate_ctx (void *arg) us->opaque = ctx_handle; /* Migrate app session as well */ - session_dgram_connect_notify (&ctx->connection, ctx->app_session_handle, &new_app_session); - ctx->app_session_handle = session_handle (new_app_session); + if (session_dgram_connect_notify (&ctx->connection, ctx->app_session_handle, + &new_app_session) < 0) + clib_warning ("srtp: could not migrate app session of udp session 0x%lx", + ctx->srtp_session_handle); + else + ctx->app_session_handle = session_handle (new_app_session); + us = session_get_from_handle (ctx->srtp_session_handle); session_migrate_accept (us); diff --git a/src/plugins/tracepath/tracepath.c b/src/plugins/tracepath/tracepath.c index 8aed07c55035..555d28ba6a45 100644 --- a/src/plugins/tracepath/tracepath.c +++ b/src/plugins/tracepath/tracepath.c @@ -245,7 +245,7 @@ show_trace_paths_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_ while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT) { - if (unformat (input, "max %d", &max_paths)) + if (unformat (input, "max %u", &max_paths)) ; else return clib_error_create ("expected 'max COUNT', got `%U'", format_unformat_error, input); @@ -332,7 +332,7 @@ show_trace_paths_graphviz_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT) { - if (unformat (input, "max %d", &max_paths)) + if (unformat (input, "max %u", &max_paths)) ; else if (unformat (input, "file %U", unformat_vlib_tmpfile, &tmp_filename)) { @@ -340,7 +340,7 @@ show_trace_paths_graphviz_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli chroot_filename = tmp_filename; tmp_filename = 0; } - else if (unformat (input, "%d", &path_index)) + else if (unformat (input, "%u", &path_index)) hash_set (path_filter, path_index, 1); /* create path filter with user-requested paths */ else { @@ -490,9 +490,9 @@ show_trace_path_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command_t while (unformat_check_input (input) != (uword) UNFORMAT_END_OF_INPUT) { - if (unformat (input, "%d", &path_index)) + if (unformat (input, "%u", &path_index)) hash_set (path_filter, path_index, 1); - else if (unformat (input, "max %d", &max_traces)) + else if (unformat (input, "max %u", &max_traces)) ; else { diff --git a/src/plugins/unittest/time_test.c b/src/plugins/unittest/time_test.c index 2a1cba5f4e7b..57c2067b1782 100644 --- a/src/plugins/unittest/time_test.c +++ b/src/plugins/unittest/time_test.c @@ -47,6 +47,30 @@ test_time_monotonicity (vlib_main_t *vm) return 0; } +static clib_error_t * +test_time_rejected_frequency_sample (vlib_main_t *vm) +{ + clib_time_t ct; + u64 before, elapsed; + + clib_time_init (&ct); + elapsed = (u64) ct.clocks_per_second; + ct.total_cpu_time += 1000 * elapsed; + before = ct.total_cpu_time; + ct.last_cpu_time -= elapsed; + ct.last_verify_cpu_time -= 100 * elapsed; + + clib_time_verify_frequency (&ct); + + if (ct.total_cpu_time < before + elapsed / 2) + return clib_error_return ( + 0, "rejected sample lost elapsed CPU ticks: before %llu, now %llu", + before, ct.total_cpu_time); + + vlib_cli_output (vm, "Rejected frequency sample preserved elapsed ticks"); + return 0; +} + /* * Test that large CPU time discontinuities (e.g., from CPU migration) * are handled correctly and don't cause integer underflow. @@ -203,6 +227,7 @@ test_time_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command clib_error_t *error = 0; int test_monotonicity = 0; int test_discontinuity = 0; + int test_rejected_frequency = 0; int test_barrier = 0; int test_all = 0; @@ -212,6 +237,8 @@ test_time_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command test_monotonicity = 1; else if (unformat (input, "discontinuity")) test_discontinuity = 1; + else if (unformat (input, "rejected-frequency")) + test_rejected_frequency = 1; else if (unformat (input, "barrier")) test_barrier = 1; else if (unformat (input, "all")) @@ -221,10 +248,14 @@ test_time_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command } if (test_all) - test_monotonicity = test_discontinuity = test_barrier = 1; + test_monotonicity = test_discontinuity = test_rejected_frequency = + test_barrier = 1; - if (!test_monotonicity && !test_discontinuity && !test_barrier) - return clib_error_return (0, "specify test: monotonicity | discontinuity | barrier | all"); + if (!test_monotonicity && !test_discontinuity && !test_rejected_frequency && + !test_barrier) + return clib_error_return ( + 0, "specify test: monotonicity | discontinuity | rejected-frequency | " + "barrier | all"); if (test_monotonicity) { @@ -240,6 +271,13 @@ test_time_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command return error; } + if (test_rejected_frequency) + { + error = test_time_rejected_frequency_sample (vm); + if (error) + return error; + } + if (test_barrier) { error = test_barrier_time_monotonicity (vm); @@ -253,7 +291,8 @@ test_time_command_fn (vlib_main_t *vm, unformat_input_t *input, vlib_cli_command VLIB_CLI_COMMAND (test_time_command, static) = { .path = "test time", - .short_help = "test time [monotonicity | discontinuity | barrier | all]", + .short_help = "test time [monotonicity | discontinuity | " + "rejected-frequency | barrier | all]", .function = test_time_command_fn, .is_mp_safe = 1, }; diff --git a/src/plugins/vhost/vhost_user.c b/src/plugins/vhost/vhost_user.c index 36637572496b..58de364a003e 100644 --- a/src/plugins/vhost/vhost_user.c +++ b/src/plugins/vhost/vhost_user.c @@ -398,6 +398,7 @@ vhost_user_socket_read (clib_file_t * uf) int n, i, j; int fd, number_of_fds = 0; int fds[VHOST_MEMORY_MAX_NREGIONS]; + u8 fd_is_owned[VHOST_MEMORY_MAX_NREGIONS] = {}; vhost_user_msg_t msg; struct msghdr mh; struct iovec iov[1]; @@ -578,6 +579,7 @@ vhost_user_socket_read (clib_file_t * uf) /* Do the mmap without barrier sync */ void *region_mmap_addr[VHOST_MEMORY_MAX_NREGIONS]; + ssize_t region_mmap_sz[VHOST_MEMORY_MAX_NREGIONS]; for (i = 0; i < msg.memory.nregions; i++) { long page_sz = get_huge_page_size (fds[i]); @@ -593,9 +595,10 @@ vhost_user_socket_read (clib_file_t * uf) { vu_log_err (vui, "failed to map memory. errno is %d", errno); for (j = 0; j < i; j++) - munmap (region_mmap_addr[j], map_sz); + munmap (region_mmap_addr[j], region_mmap_sz[j]); goto close_socket; } + region_mmap_sz[i] = map_sz; vu_log_debug (vui, "map memory region %d addr 0 len 0x%lx fd %d " "mapped 0x%lx page_sz 0x%x", i, map_sz, fds[i], region_mmap_addr[i], page_sz); @@ -615,6 +618,7 @@ vhost_user_socket_read (clib_file_t * uf) vui->region_mmap_addr[i] += vui->regions[i].mmap_offset; vui->region_mmap_fd[i] = fds[i]; + fd_is_owned[i] = 1; vui->nregions++; } @@ -791,6 +795,7 @@ vhost_user_socket_read (clib_file_t * uf) ((vui - vhost_user_main.vhost_user_interfaces) << 8) + q; template.description = format (0, "vhost user"); vui->vrings[q].callfd_idx = clib_file_add (&file_main, &template); + fd_is_owned[0] = 1; } else vui->vrings[q].callfd_idx = ~0; @@ -830,6 +835,7 @@ vhost_user_socket_read (clib_file_t * uf) (((uword) (vui - vhost_user_main.vhost_user_interfaces)) << 8) + q; vui->vrings[q].kickfd_idx = clib_file_add (&file_main, &template); + fd_is_owned[0] = 1; } else { @@ -862,6 +868,7 @@ vhost_user_socket_read (clib_file_t * uf) goto close_socket; vui->vrings[q].errfd = fds[0]; + fd_is_owned[0] = 1; } else vui->vrings[q].errfd = -1; @@ -1011,6 +1018,8 @@ vhost_user_socket_read (clib_file_t * uf) vui->log_base_addr = log_base_addr; vui->log_base_addr += msg.log.offset; vui->log_size = msg.log.size; + close (fd); + fds[0] = -1; vlib_worker_thread_barrier_release (vm); msg.flags |= 4; @@ -1090,6 +1099,9 @@ vhost_user_socket_read (clib_file_t * uf) return 0; close_socket: + for (i = 0; i < number_of_fds; i++) + if (!fd_is_owned[i] && fds[i] != -1) + close (fds[i]); vlib_worker_thread_barrier_sync (vm); vhost_user_if_disconnect (vui); vlib_worker_thread_barrier_release (vm); diff --git a/src/scripts/version b/src/scripts/version index b8748af1588d..7bfe9daf7655 100755 --- a/src/scripts/version +++ b/src/scripts/version @@ -41,10 +41,17 @@ if [ -z "${POINT}" ]; then fi fi +# Prefer git for the commit description, but fall back to the commit count +# and hash already carried in vstring when git cannot supply them. A repo +# without any matching tag (e.g. a fork with no tags pushed) makes +# git describe fail; without this the commit is dropped and every build of +# such a repo gets the same version string, which makes package managers +# treat a newer build as already installed. git rev-parse 2> /dev/null if [ $? == 0 ]; then - CMT=$(git describe --dirty --long --match 'v*'| cut -s -d- -f3,4) -else + CMT=$(git describe --dirty --long --match 'v*' 2> /dev/null | cut -s -d- -f3,4) +fi +if [ -z "${CMT}" ]; then CMT=$(echo ${vstring} | cut -s -d- -f3,4) fi CMTR=$(echo $CMT | sed 's/-/_/') diff --git a/src/svm/fifo_segment.c b/src/svm/fifo_segment.c index f04a67ce6caf..5f569f5548cc 100644 --- a/src/svm/fifo_segment.c +++ b/src/svm/fifo_segment.c @@ -1038,10 +1038,11 @@ fifo_segment_detach_fifo (fifo_segment_t *fs, svm_fifo_t **f) *f = 0; } -void +int fifo_segment_attach_fifo (fifo_segment_t *fs, svm_fifo_t **f, u32 slice_index) { - svm_fifo_chunk_t *c, *nc, *pc = 0; + svm_fifo_chunk_t *c, *nc, *pc = 0, *attach_chunks = 0; + svm_fifo_shared_t *attach_hdr = 0; fifo_slice_private_t *pfss; fifo_segment_slice_t *fss; svm_fifo_t *nf, *of; @@ -1059,21 +1060,39 @@ fifo_segment_attach_fifo (fifo_segment_t *fs, svm_fifo_t **f, u32 slice_index) /* Allocate shared hdr and chunks to be collected at detach in return * for those that are being attached now */ of = *f; - svm_fifo_hdr_at_attach (of) = fsh_try_alloc_fifo_hdr (fs->h, fss); + attach_hdr = fsh_try_alloc_fifo_hdr (fs->h, fss); + if (!attach_hdr) + goto error; c = fs_chunk_ptr (fs->h, nf->shr->start_chunk); - svm_fifo_chunks_at_attach (of) = pc = - fsh_try_alloc_chunk (fs->h, fss, c->length); + attach_chunks = pc = fsh_try_alloc_chunk (fs->h, fss, c->length); + if (!attach_chunks) + goto error; while ((c = fs_chunk_ptr (fs->h, c->next))) { nc = fsh_try_alloc_chunk (fs->h, fss, c->length); + if (!nc) + goto error; pc->next = fs_chunk_sptr (fs->h, nc); pc = nc; } + svm_fifo_hdr_at_attach (of) = attach_hdr; + svm_fifo_chunks_at_attach (of) = attach_chunks; nf->shr->slice_index = slice_index; *f = nf; + return 0; + +error: + fsh_slice_collect_chunks (fs->h, fss, attach_chunks); + if (attach_hdr) + fss_fifo_free_list_push (fs->h, fss, attach_hdr); + if (nf->flags & SVM_FIFO_F_LL_TRACKED) + pfss_fifo_del_active_list (pfss, nf); + fss->virtual_mem -= svm_fifo_size (nf); + fs_fifo_free (fs, nf, slice_index); + return -1; } uword diff --git a/src/svm/fifo_segment.h b/src/svm/fifo_segment.h index 90ea7be49754..52a63c773999 100644 --- a/src/svm/fifo_segment.h +++ b/src/svm/fifo_segment.h @@ -140,8 +140,8 @@ void fifo_segment_free_fifo (fifo_segment_t * fs, svm_fifo_t * f); void fifo_segment_free_client_fifo (fifo_segment_t *fs, svm_fifo_t *f); void fifo_segment_detach_fifo (fifo_segment_t *fs, svm_fifo_t **f); -void fifo_segment_attach_fifo (fifo_segment_t *fs, svm_fifo_t **f, - u32 slice_index); +int fifo_segment_attach_fifo (fifo_segment_t *fs, svm_fifo_t **f, + u32 slice_index); uword fifo_segment_fifo_offset (svm_fifo_t *f); /** diff --git a/src/svm/ssvm.c b/src/svm/ssvm.c index 41a7744e2b35..7c00cacd2f3c 100644 --- a/src/svm/ssvm.c +++ b/src/svm/ssvm.c @@ -235,6 +235,8 @@ ssvm_server_init_memfd (ssvm_private_t * memfd) if (log2_page_size == 0) { clib_unix_warning ("cannot determine page size"); + close (memfd->fd); + memfd->fd = -1; return SSVM_API_ERROR_CREATE_FAILURE; } @@ -243,6 +245,8 @@ ssvm_server_init_memfd (ssvm_private_t * memfd) if ((ftruncate (memfd->fd, n_pages << log2_page_size)) == -1) { clib_unix_warning ("memfd ftruncate failure"); + close (memfd->fd); + memfd->fd = -1; return SSVM_API_ERROR_CREATE_FAILURE; } @@ -253,6 +257,7 @@ ssvm_server_init_memfd (ssvm_private_t * memfd) { clib_unix_warning ("memfd map (fd %d)", memfd->fd); close (memfd->fd); + memfd->fd = -1; return SSVM_API_ERROR_CREATE_FAILURE; } @@ -269,6 +274,14 @@ ssvm_server_init_memfd (ssvm_private_t * memfd) sh->heap = clib_mem_create_heap (((u8 *) sh) + page_size, memfd->ssvm_size - page_size, 1 /* locked */ , "ssvm server memfd"); + if (sh->heap == 0) + { + clib_unix_warning ("heap alloc"); + clib_mem_vm_unmap (memfd->sh); + close (memfd->fd); + memfd->fd = -1; + return SSVM_API_ERROR_CREATE_FAILURE; + } oldheap = ssvm_push_heap (sh); sh->name = format (0, "%s", memfd->name, 0); ssvm_pop_heap (oldheap); @@ -384,7 +397,8 @@ ssvm_server_init_private (ssvm_private_t * ssvm) if (heap == 0) { clib_unix_warning ("heap alloc"); - return -1; + clib_mem_vm_unmap (sh); + return SSVM_API_ERROR_CREATE_FAILURE; } rnd_size = clib_mem_get_heap_free_space (heap); diff --git a/src/vcl/ldp.c b/src/vcl/ldp.c index 210c6a2d5871..dc258c28f2df 100644 --- a/src/vcl/ldp.c +++ b/src/vcl/ldp.c @@ -2038,8 +2038,13 @@ getsockopt (int fd, int level, int optname, rv = -EFAULT; break; case TCP_CONGESTION: + if (*optlen < (socklen_t) sizeof ("cubic")) + { + rv = -EINVAL; + break; + } + clib_memcpy_fast (optval, "cubic", sizeof ("cubic")); *optlen = strlen ("cubic"); - strncpy (optval, "cubic", *optlen + 1); rv = 0; break; default: @@ -2086,7 +2091,8 @@ getsockopt (int fd, int level, int optname, break; case SO_PROTOCOL: rv = vls_attr (vlsh, VPPCOM_ATTR_GET_PROTOCOL, optval, optlen); - *(int *) optval = *(int *) optval ? SOCK_DGRAM : SOCK_STREAM; + if (rv == VPPCOM_OK && *optlen >= sizeof (int)) + *(int *) optval = *(int *) optval ? SOCK_DGRAM : SOCK_STREAM; break; case SO_SNDBUF: rv = vls_attr (vlsh, VPPCOM_ATTR_GET_TX_FIFO_LEN, diff --git a/src/vcl/vppcom.c b/src/vcl/vppcom.c index f834910c7f60..00d5c4728128 100644 --- a/src/vcl/vppcom.c +++ b/src/vcl/vppcom.c @@ -295,7 +295,7 @@ vcl_send_worker_rpc (u32 dst_wrk_index, void *data, u32 data_len) int ret = -1; if (data_len > sizeof (mp->data)) - goto done; + return -1; clib_spinlock_lock (&vcm->workers_lock); diff --git a/src/vlib/buffer_funcs.h b/src/vlib/buffer_funcs.h index 85e3c86244b4..2dbec57cfad7 100644 --- a/src/vlib/buffer_funcs.h +++ b/src/vlib/buffer_funcs.h @@ -1451,11 +1451,18 @@ vlib_buffer_clone_at_offset (vlib_main_t * vm, u32 src_buffer, u32 * buffers, while (n_buffers > VLIB_BUFFER_MAX_CLONE) { vlib_buffer_t *copy; + u16 n_cloned_batch; + copy = vlib_buffer_copy (vm, s); if (copy) - n_cloned += - vlib_buffer_clone_255 (vm, vlib_get_buffer_index (vm, copy), (buffers + n_cloned), - VLIB_BUFFER_MAX_CLONE, head_end_offset, offset); + { + n_cloned_batch = vlib_buffer_clone_255 ( + vm, vlib_get_buffer_index (vm, copy), buffers + n_cloned, + VLIB_BUFFER_MAX_CLONE, head_end_offset, offset); + if (n_cloned_batch == 0) + vlib_buffer_free_one (vm, vlib_get_buffer_index (vm, copy)); + n_cloned += n_cloned_batch; + } n_buffers -= VLIB_BUFFER_MAX_CLONE; } n_cloned += vlib_buffer_clone_255 (vm, src_buffer, buffers + n_cloned, diff --git a/src/vlib/handoff.c b/src/vlib/handoff.c index 1ceb3fb96438..e061661c2ecb 100644 --- a/src/vlib/handoff.c +++ b/src/vlib/handoff.c @@ -204,7 +204,7 @@ vlib_handoff_queue_dequeue_inline (vlib_main_t *vm, vlib_handoff_queue_main_t *h if (n_deq) { hq->n_vectors += n_deq; - __atomic_store_n (&hq->head, head, __ATOMIC_RELAXED); + __atomic_store_n (&hq->head, head, __ATOMIC_RELEASE); if (n_deq >= dequeue_vector_limit) __atomic_fetch_or (&vm->handoff_queue_pending_bmp, hqm->queue_bit, __ATOMIC_RELAXED); } @@ -355,7 +355,7 @@ vlib_handoff_enqueue_one_thread (vlib_main_t *vm, vlib_node_runtime_t *node, retry: tail = __atomic_load_n (&hq->tail, __ATOMIC_RELAXED); - head = __atomic_load_n (&hq->head, __ATOMIC_RELAXED); + head = __atomic_load_n (&hq->head, __ATOMIC_ACQUIRE); n_avail = head + size - tail; if (n_avail < n_req) @@ -651,6 +651,8 @@ vlib_handoff_queue_resize (u32 index, u32 queue_size) hqm = vec_elt_at_index (vm->handoff_queue_mains, index); old_queue_size = hqm->size * VLIB_HANDOFF_QUEUE_SLOT_N_ELTS; + vlib_worker_thread_barrier_sync (vm); + vec_foreach_index (thread_index, hqm->vlib_handoff_queues) { vlib_handoff_queue_t *old_hq = hqm->vlib_handoff_queues[thread_index]; @@ -664,6 +666,7 @@ vlib_handoff_queue_resize (u32 index, u32 queue_size) vec_foreach (q, new_queues) clib_mem_free (q[0]); vec_free (new_queues); + vlib_worker_thread_barrier_release (vm); return clib_error_return (0, "queue has pending buffers using %u elements, " "size %u is too small", @@ -692,6 +695,8 @@ vlib_handoff_queue_resize (u32 index, u32 queue_size) vm->handoff_queue_mains = this_vm->handoff_queue_mains; } + vlib_worker_thread_barrier_release (this_vm); + return 0; } @@ -734,6 +739,7 @@ vlib_handoff_alloc_queues (vlib_handoff_alloc_queues_args_t *a) } hqm_index = vec_len (vm->handoff_queue_mains); + vlib_worker_thread_barrier_sync (this_vm); vec_add2 (vm->handoff_queue_mains, hqm_at_index, 1); hqm_at_index[0] = hqm; @@ -748,6 +754,8 @@ vlib_handoff_alloc_queues (vlib_handoff_alloc_queues_args_t *a) vm->handoff_queue_mains = this_vm->handoff_queue_mains; } + vlib_worker_thread_barrier_release (this_vm); + return hqm_index; } #endif diff --git a/src/vlib/physmem.c b/src/vlib/physmem.c index afa753aeb14c..a842230e26e0 100644 --- a/src/vlib/physmem.c +++ b/src/vlib/physmem.c @@ -57,14 +57,14 @@ vlib_physmem_shared_map_create (vlib_main_t * vm, char *name, uword size, map->log2_page_size = a->log2_subpage_sz; map->numa_node = a->numa_node; - for (i = 0; i < a->n_pages; i++) + for (i = 0; i < map->n_pages; i++) { - uword pa = - clib_pmalloc_get_pa (pm, (u8 *) va + (i << a->log2_subpage_sz)); + void *page_va = (u8 *) va + (i << a->log2_subpage_sz); + uword pa = clib_pmalloc_get_pa (pm, page_va); /* maybe iova */ if (pa == 0) - pa = pointer_to_uword (va); + pa = pointer_to_uword (page_va); vec_add1 (map->page_table, pa); } diff --git a/src/vlibapi/api.h b/src/vlibapi/api.h index 44e07531dc9f..4270a44f0467 100644 --- a/src/vlibapi/api.h +++ b/src/vlibapi/api.h @@ -88,8 +88,8 @@ vl_msg_api_get_msg_length_inline (void *msg_arg) int vl_msg_api_rx_trace_enabled (api_main_t * am); int vl_msg_api_tx_trace_enabled (api_main_t * am); void vl_msg_api_trace (api_main_t * am, vl_api_trace_t * tp, void *msg); -int vl_msg_api_trace_onoff (api_main_t * am, vl_api_trace_which_t which, - int onoff); +void vl_msg_api_trace_with_size (api_main_t *am, vl_api_trace_t *tp, void *msg, uword msg_len); +int vl_msg_api_trace_onoff (api_main_t *am, vl_api_trace_which_t which, int onoff); int vl_msg_api_trace_free (api_main_t * am, vl_api_trace_which_t which); int vl_msg_api_trace_configure (api_main_t * am, vl_api_trace_which_t which, u32 nitems); diff --git a/src/vlibapi/api_shared.c b/src/vlibapi/api_shared.c index 9cb9896993e2..a6e9addf3b4c 100644 --- a/src/vlibapi/api_shared.c +++ b/src/vlibapi/api_shared.c @@ -59,19 +59,33 @@ vl_msg_api_tx_trace_enabled (api_main_t * am) * vl_msg_api_trace */ void -vl_msg_api_trace (api_main_t * am, vl_api_trace_t * tp, void *msg) +vl_msg_api_trace_with_size (api_main_t *am, vl_api_trace_t *tp, void *msg, uword msg_len) { u8 **this_trace; u8 **old_trace; u8 *msg_copy; - u32 length; - u16 msg_id = clib_net_to_host_u16 (*((u16 *) msg)); - vl_api_msg_data_t *m = vl_api_get_msg_data (am, msg_id); - msgbuf_t *header = (msgbuf_t *) (((u8 *) msg) - offsetof (msgbuf_t, data)); + uword length; + u16 msg_id; + vl_api_msg_data_t *m; + if (PREDICT_FALSE (msg_len < sizeof (u16))) + return; + + msg_id = clib_net_to_host_u16 (*((u16 *) msg)); + m = vl_api_get_msg_data (am, msg_id); if (!m || !m->trace_enable) return; + /* + * Copy the whole message. msg_len is the size of the message buffer, and + * a message with a variable length array member must be copied in full: a + * shorter copy keeps the larger element count, so the endian, format and + * json handlers read and write past the end of the copy. m->trace_size is + * not a bound for the copy, it sizes the initializer array printed by + * "api trace initializers". + */ + length = msg_len; + msg_copy = 0; if (tp->nitems == 0) @@ -97,13 +111,20 @@ vl_msg_api_trace (api_main_t * am, vl_api_trace_t * tp, void *msg) this_trace = old_trace; } - length = clib_net_to_host_u32 (header->data_len); - vec_validate (msg_copy, length - 1); clib_memcpy_fast (msg_copy, msg, length); *this_trace = msg_copy; } +void +vl_msg_api_trace (api_main_t *am, vl_api_trace_t *tp, void *msg) +{ + msgbuf_t *header = (msgbuf_t *) (((u8 *) msg) - offsetof (msgbuf_t, data)); + u32 msg_len = clib_net_to_host_u32 (header->data_len); + + vl_msg_api_trace_with_size (am, tp, msg, msg_len); +} + int vl_msg_api_trace_onoff (api_main_t * am, vl_api_trace_which_t which, int onoff) @@ -211,6 +232,8 @@ vl_msg_api_trace_write_one (api_main_t *am, u8 *msg, FILE *fp) int tlen, slen; u32 msg_length = vec_len (msg); + if (PREDICT_FALSE (msg_length < sizeof (u16))) + return -1; vec_validate (tmpmem, msg_length - 1); clib_memcpy_fast (tmpmem, msg, msg_length); u16 id = clib_net_to_host_u16 (*((u16 *) msg)); @@ -484,8 +507,18 @@ always_inline void msg_handler_internal (api_main_t *am, void *the_msg, uword msg_len, int trace_it, int do_it, int free_it) { - u16 id = clib_net_to_host_u16 (*((u16 *) the_msg)); - vl_api_msg_data_t *m = vl_api_get_msg_data (am, id); + u16 id; + vl_api_msg_data_t *m; + + if (PREDICT_FALSE (msg_len < sizeof (u16))) + { + if (free_it) + vl_msg_api_free (the_msg); + return; + } + + id = clib_net_to_host_u16 (*((u16 *) the_msg)); + m = vl_api_get_msg_data (am, id); if (PREDICT_FALSE (am->elog_trace_api_messages)) { @@ -508,7 +541,7 @@ msg_handler_internal (api_main_t *am, void *the_msg, uword msg_len, if (m && m->handler) { if (trace_it) - vl_msg_api_trace (am, am->rx_trace, the_msg); + vl_msg_api_trace_with_size (am, am->rx_trace, the_msg, msg_len); if (am->msg_print_flag) { diff --git a/src/vlibmemory/memory_api.c b/src/vlibmemory/memory_api.c index 0ae7fe6f3703..451e38ba814d 100644 --- a/src/vlibmemory/memory_api.c +++ b/src/vlibmemory/memory_api.c @@ -751,9 +751,9 @@ void (*vl_mem_api_fuzz_hook) (u16, void *); /* This is only to be called from a vlib/vnet app */ static void -vl_mem_api_handler_with_vm_node (api_main_t *am, svm_region_t *vlib_rp, - void *the_msg, vlib_main_t *vm, - vlib_node_runtime_t *node, u8 is_private) +vl_mem_api_handler_with_vm_node (api_main_t *am, svm_region_t *vlib_rp, void *the_msg, + vlib_main_t *vm, vlib_node_runtime_t *node, u8 is_private, + uword msg_len) { u16 id = clib_net_to_host_u16 (*((u16 *) the_msg)); vl_api_msg_data_t *m = vl_api_get_msg_data (am, id); @@ -784,7 +784,7 @@ vl_mem_api_handler_with_vm_node (api_main_t *am, svm_region_t *vlib_rp, handler = (void *) m->handler; if (PREDICT_FALSE (am->rx_trace && am->rx_trace->enabled)) - vl_msg_api_trace (am, am->rx_trace, the_msg); + vl_msg_api_trace_with_size (am, am->rx_trace, the_msg, msg_len); if (PREDICT_FALSE (am->msg_print_flag)) { @@ -880,10 +880,35 @@ vl_mem_api_handler_with_vm_node (api_main_t *am, svm_region_t *vlib_rp, } } +/* + * Validate that the API message at mp and the body length it announces lie + * inside the shared region vlib_rp. Both mp and msgbuf_t.data_len are + * written by the API client, so neither is trusted: a bogus pointer, or a + * bogus length, would otherwise make the trace path and VL_MSG_API_UNPOISON + * read outside the region. Returns 0 and stores the length on success. + */ +static int +vl_mem_api_msg_len (svm_region_t *vlib_rp, uword mp, uword *msg_len) +{ + uword region_start = (uword) vlib_rp->virtual_base; + uword region_end = region_start + vlib_rp->virtual_size; + msgbuf_t *hdr; + + if (mp < region_start + sizeof (msgbuf_t) || mp > region_end) + return -1; + + hdr = (msgbuf_t *) (mp - sizeof (msgbuf_t)); + *msg_len = (uword) clib_net_to_host_u32 (hdr->data_len); + + if (*msg_len > region_end - mp) + return -1; + + return 0; +} + static inline int -void_mem_api_handle_msg_i (api_main_t * am, svm_region_t * vlib_rp, - vlib_main_t * vm, vlib_node_runtime_t * node, - u8 is_private) +void_mem_api_handle_msg_i (api_main_t *am, svm_region_t *vlib_rp, vlib_main_t *vm, + vlib_node_runtime_t *node, u8 is_private) { svm_queue_t *q; uword mp; @@ -892,9 +917,16 @@ void_mem_api_handle_msg_i (api_main_t * am, svm_region_t * vlib_rp, if (!svm_queue_sub2 (q, (u8 *) & mp)) { + uword msg_len; + + if (vl_mem_api_msg_len (vlib_rp, mp, &msg_len)) + { + clib_warning ("dropping API message outside the region (0x%lx)", mp); + return 0; + } + VL_MSG_API_UNPOISON ((void *) mp); - vl_mem_api_handler_with_vm_node (am, vlib_rp, (void *) mp, vm, node, - is_private); + vl_mem_api_handler_with_vm_node (am, vlib_rp, (void *) mp, vm, node, is_private, msg_len); return 0; } return -1; @@ -942,9 +974,16 @@ vl_mem_api_handle_rpc (vlib_main_t * vm, vlib_node_runtime_t * node) vl_msg_api_barrier_sync (); for (i = 0; i < vec_len (vm->processing_rpc_requests); i++) { + uword msg_len; + mp = vm->processing_rpc_requests[i]; - vl_mem_api_handler_with_vm_node (am, am->vlib_rp, (void *) mp, vm, - node, 0 /* is_private */); + if (vl_mem_api_msg_len (am->vlib_rp, mp, &msg_len)) + { + clib_warning ("dropping RPC message outside the region (0x%lx)", mp); + continue; + } + vl_mem_api_handler_with_vm_node (am, am->vlib_rp, (void *) mp, vm, node, + 0 /* is_private */, msg_len); } vl_msg_api_barrier_release (); } diff --git a/src/vnet/adj/adj_l2.c b/src/vnet/adj/adj_l2.c index 7e9e985ecb71..7bfc42c38ef3 100644 --- a/src/vnet/adj/adj_l2.c +++ b/src/vnet/adj/adj_l2.c @@ -105,7 +105,7 @@ adj_l2_rewrite_inline (vlib_main_t * vm, p0->current_length += rw_len0; tx_sw_if_index0 = adj0[0].rewrite_header.sw_if_index; - if (is_midchain) + if (is_midchain && adj0->sub_type.midchain.fixup_func) { adj0->sub_type.midchain.fixup_func( vm, adj0, p0, diff --git a/src/vnet/adj/adj_nsh.c b/src/vnet/adj/adj_nsh.c index 1480f3fa54a7..19728320af58 100644 --- a/src/vnet/adj/adj_nsh.c +++ b/src/vnet/adj/adj_nsh.c @@ -101,7 +101,7 @@ adj_nsh_rewrite_inline (vlib_main_t * vm, p0->current_length += rw_len0; tx_sw_if_index0 = adj0[0].rewrite_header.sw_if_index; - if (is_midchain) + if (is_midchain && adj0->sub_type.midchain.fixup_func) { adj0->sub_type.midchain.fixup_func( vm, adj0, p0, diff --git a/src/vnet/dev/handlers.c b/src/vnet/dev/handlers.c index 06f808d3e05e..58750499667c 100644 --- a/src/vnet/dev/handlers.c +++ b/src/vnet/dev/handlers.c @@ -306,11 +306,6 @@ vnet_dev_set_interface_next_node (vnet_main_t *vnm, u32 hw_if_index, } intf->redirect_to_node = 1; } - intf->rx_next_index = - node_index == ~0 ? - vnet_dev_default_next_index_by_port_type[di->port->attr.type] : - node_index; - if (runtime_update) { foreach_vnet_dev_port_rx_queue (rxq, di->port) diff --git a/src/vnet/devices/netlink.c b/src/vnet/devices/netlink.c index 59d4aa2d5233..90fd4c08680f 100644 --- a/src/vnet/devices/netlink.c +++ b/src/vnet/devices/netlink.c @@ -92,7 +92,8 @@ vnet_netlink_msg_send (vnet_netlink_msg_t *m, vnet_netlink_msg_t **replies) { struct nlmsgerr *e = (struct nlmsgerr *) NLMSG_DATA (nh); if (e->error) - err = clib_error_return (0, "netlink error %d", e->error); + err = clib_error_return_code (0, -e->error, 0, "netlink error %d", + e->error); goto done; } @@ -323,7 +324,7 @@ vnet_netlink_add_ip4_addr (int ifindex, void *addr, int pfx_len) vnet_netlink_msg_add_rtattr (&m, IFA_ADDRESS, addr, 4); err = vnet_netlink_msg_send (&m, NULL); if (err) - err = clib_error_return (0, "add ip4 addr %U", format_clib_error, err); + err = clib_error_return (err, "add ip4 addr"); return err; } @@ -346,7 +347,7 @@ vnet_netlink_add_ip6_addr (int ifindex, void *addr, int pfx_len) vnet_netlink_msg_add_rtattr (&m, IFA_ADDRESS, addr, 16); err = vnet_netlink_msg_send (&m, NULL); if (err) - err = clib_error_return (0, "add ip6 addr %U", format_clib_error, err); + err = clib_error_return (err, "add ip6 addr"); return err; } @@ -417,8 +418,14 @@ vnet_netlink_del_ip4_addr (int ifindex, void *addr, int pfx_len) vnet_netlink_msg_add_rtattr (&m, IFA_LOCAL, addr, 4); vnet_netlink_msg_add_rtattr (&m, IFA_ADDRESS, addr, 4); err = vnet_netlink_msg_send (&m, NULL); + if (err && clib_error_get_code (err) == EADDRNOTAVAIL && + !(err->flags & CLIB_ERROR_ERRNO_VALID)) + { + clib_error_free (err); + return 0; + } if (err) - err = clib_error_return (0, "del ip4 addr %U", format_clib_error, err); + err = clib_error_return (err, "del ip4 addr"); return err; } @@ -439,7 +446,13 @@ vnet_netlink_del_ip6_addr (int ifindex, void *addr, int pfx_len) vnet_netlink_msg_add_rtattr (&m, IFA_LOCAL, addr, 16); vnet_netlink_msg_add_rtattr (&m, IFA_ADDRESS, addr, 16); err = vnet_netlink_msg_send (&m, NULL); + if (err && clib_error_get_code (err) == EADDRNOTAVAIL && + !(err->flags & CLIB_ERROR_ERRNO_VALID)) + { + clib_error_free (err); + return 0; + } if (err) - err = clib_error_return (0, "del ip6 addr %U", format_clib_error, err); + err = clib_error_return (err, "del ip6 addr"); return err; } diff --git a/src/vnet/dpo/replicate_dpo.c b/src/vnet/dpo/replicate_dpo.c index 834fe1df2304..3267c26c701d 100644 --- a/src/vnet/dpo/replicate_dpo.c +++ b/src/vnet/dpo/replicate_dpo.c @@ -751,7 +751,7 @@ replicate_inline (vlib_main_t * vm, const replicate_t *rep0; vlib_buffer_t * b0, *c0; const dpo_id_t *dpo0; - u8 num_cloned; + u16 num_cloned; bi0 = from[0]; from += 1; diff --git a/src/vnet/fib/fib_attached_export.c b/src/vnet/fib/fib_attached_export.c index 60d99f5748d4..480f0e34f8df 100644 --- a/src/vnet/fib/fib_attached_export.c +++ b/src/vnet/fib/fib_attached_export.c @@ -233,6 +233,16 @@ fib_attached_export_import (fib_entry_t *fib_entry, fib_ae_import_t *import; fib_node_index_t fei; + /* + * The exporter FIB index is derived from a route's resolving interface. + * Interfaces that are not bound to a table, and interfaces that have + * been deleted, yield ~0. fib_table_get() would then index the FIB pool + * out of range and fib_table_lookup_exact_match() would dereference the + * result. Nothing to export to, so do nothing. + */ + if (FIB_NODE_INDEX_INVALID == export_fib) + return; + /* * save index for later post-realloc retrieval */ diff --git a/src/vnet/fib/fib_entry.c b/src/vnet/fib/fib_entry.c index 688195a90010..f39aa0d81fdf 100644 --- a/src/vnet/fib/fib_entry.c +++ b/src/vnet/fib/fib_entry.c @@ -605,7 +605,16 @@ fib_entry_post_flag_update_actions (fib_entry_t *fib_entry, fib_entry_get_proto(fib_entry), sw_if_index); } - fib_attached_export_import(fib_entry, new_fib_index); + + /* + * The resolving interface may be unbound, or bound to a table that + * has since been removed. fib_table_get_index_for_sw_if_index() + * reports that as ~0. There is no exporter table to import from, so + * leave the entry alone; it will be revisited when the interface is + * bound to a table again. + */ + if (~0 != new_fib_index) + fib_attached_export_import(fib_entry, new_fib_index); } else if (was_import && !is_import) { diff --git a/src/vnet/fib/fib_table.c b/src/vnet/fib/fib_table.c index a928e77bbbc1..939448ee0870 100644 --- a/src/vnet/fib/fib_table.c +++ b/src/vnet/fib/fib_table.c @@ -61,11 +61,53 @@ fib_table_lookup_i (fib_table_t *fib_table, return (FIB_NODE_INDEX_INVALID); } +/* + * fib_table_get() indexes the per-protocol FIB pool without validating the + * index. Callers that derive the index from an interface binding can be + * handed ~0, and an interface that has been deleted keeps a stale index until + * its binding is torn down. Validate here so that a bad index produces an + * empty lookup rather than a read through a wild pointer. + */ +fib_table_t * +fib_table_get_or_null (u32 fib_index, fib_protocol_t proto) +{ + if (~0 == fib_index) + return (NULL); + + switch (proto) + { + case FIB_PROTOCOL_IP4: + if (fib_index >= vec_len (ip4_main.fibs) || + pool_is_free_index (ip4_main.fibs, fib_index)) + return (NULL); + break; + case FIB_PROTOCOL_IP6: + if (fib_index >= vec_len (ip6_main.fibs) || + pool_is_free_index (ip6_main.fibs, fib_index)) + return (NULL); + break; + case FIB_PROTOCOL_MPLS: + if (fib_index >= vec_len (mpls_main.fibs) || + pool_is_free_index (mpls_main.fibs, fib_index)) + return (NULL); + break; + } + + return (fib_table_get (fib_index, proto)); +} + fib_node_index_t fib_table_lookup (u32 fib_index, const fib_prefix_t *prefix) { - return (fib_table_lookup_i(fib_table_get(fib_index, prefix->fp_proto), prefix)); + fib_table_t *fib_table; + + fib_table = fib_table_get_or_null (fib_index, prefix->fp_proto); + + if (NULL == fib_table) + return (FIB_NODE_INDEX_INVALID); + + return (fib_table_lookup_i(fib_table, prefix)); } static inline fib_node_index_t @@ -94,9 +136,14 @@ fib_node_index_t fib_table_lookup_exact_match (u32 fib_index, const fib_prefix_t *prefix) { - return (fib_table_lookup_exact_match_i(fib_table_get(fib_index, - prefix->fp_proto), - prefix)); + fib_table_t *fib_table; + + fib_table = fib_table_get_or_null (fib_index, prefix->fp_proto); + + if (NULL == fib_table) + return (FIB_NODE_INDEX_INVALID); + + return (fib_table_lookup_exact_match_i (fib_table, prefix)); } static fib_node_index_t @@ -1090,7 +1137,7 @@ fib_table_get_table_id_for_sw_if_index (fib_protocol_t proto, { fib_table_t *fib_table; - fib_table = fib_table_get(fib_table_get_index_for_sw_if_index( + fib_table = fib_table_get_or_null(fib_table_get_index_for_sw_if_index( proto, sw_if_index), proto); @@ -1103,7 +1150,7 @@ fib_table_get_table_id (u32 fib_index, { fib_table_t *fib_table; - fib_table = fib_table_get(fib_index, proto); + fib_table = fib_table_get_or_null(fib_index, proto); return ((NULL != fib_table ? fib_table->ft_table_id : ~0)); } diff --git a/src/vnet/fib/fib_table.h b/src/vnet/fib/fib_table.h index b22dd7f71d5c..24510dba10b4 100644 --- a/src/vnet/fib/fib_table.h +++ b/src/vnet/fib/fib_table.h @@ -904,6 +904,18 @@ extern u32 fib_table_get_num_entries(u32 fib_index, extern fib_table_t *fib_table_get(fib_node_index_t index, fib_protocol_t proto); +/** + * @brief + * Get a pointer to a FIB table, validating the index + * + * Unlike fib_table_get(), this rejects ~0, an out-of-range index and a + * free pool slot, and returns NULL instead. Callers that derive the index + * from an interface binding or from another untrusted source must use this + * accessor. + */ +extern fib_table_t *fib_table_get_or_null(u32 fib_index, + fib_protocol_t proto); + /** * @brief return code controlling how a table walk proceeds */ diff --git a/src/vnet/interface/rx_queue.c b/src/vnet/interface/rx_queue.c index c5ca41bb59ac..0542da4b7108 100644 --- a/src/vnet/interface/rx_queue.c +++ b/src/vnet/interface/rx_queue.c @@ -150,7 +150,10 @@ vnet_hw_if_set_rx_queue_file_index (vnet_main_t *vnm, u32 queue_index, vnet_hw_interface_t *hi = vnet_get_hw_interface (vnm, rxq->hw_if_index); rxq->file_index = file_index; - clib_file_set_polling_thread (&file_main, file_index, rxq->thread_index); + /* ~0 means the queue has no clib file; any other value must name a live + one, because the poller is programmed for it. */ + if (file_index != ~0) + clib_file_set_polling_thread (&file_main, file_index, rxq->thread_index); log_debug ("set_file_index: interface %v queue-id %u file-index %u", hi->name, rxq->queue_id, file_index); } @@ -223,7 +226,23 @@ vnet_hw_if_set_rx_queue_thread_index (vnet_main_t *vnm, u32 queue_index, rxq->thread_index = thread_index; if (rxq->file_index != ~0) - clib_file_set_polling_thread (&file_main, rxq->file_index, thread_index); + { + clib_file_t *f = clib_file_get (&file_main, rxq->file_index); + + if (f) + clib_file_set_polling_thread (&file_main, rxq->file_index, + thread_index); + else + { + /* The file was removed without clearing the queue's copy, which + drivers do when they close their interrupt files. Drop the stale + index rather than resolving it again on the next placement. */ + log_debug ("set_rx_queue_thread_index: interface %v queue-id %u " + "file-index %u is gone", + hi->name, rxq->queue_id, rxq->file_index); + rxq->file_index = ~0; + } + } log_debug ("set_rx_queue_thread_index: interface %v queue-id %u " "thread-index set to %u", diff --git a/src/vnet/interface_api.c b/src/vnet/interface_api.c index d7f6234f37ba..f17855867d7a 100644 --- a/src/vnet/interface_api.c +++ b/src/vnet/interface_api.c @@ -664,8 +664,9 @@ vl_api_sw_interface_get_table_t_handler (vl_api_sw_interface_get_table_t * mp) fib_index = fib_table_get_index_for_sw_if_index (fib_proto, sw_if_index); if (fib_index != ~0) { - fib_table = fib_table_get (fib_index, fib_proto); - table_id = fib_table->ft_table_id; + fib_table = fib_table_get_or_null (fib_index, fib_proto); + if (fib_table) + table_id = fib_table->ft_table_id; } BAD_SW_IF_INDEX_LABEL; diff --git a/src/vnet/ip/ip6_ll_table.c b/src/vnet/ip/ip6_ll_table.c index b7ecd8595c61..efbdb88c02bf 100644 --- a/src/vnet/ip/ip6_ll_table.c +++ b/src/vnet/ip/ip6_ll_table.c @@ -246,9 +246,9 @@ ip6_ll_show_fib (vlib_main_t * vm, if (~0 == fib_index) continue; - fib_table = fib_table_get (fib_index, FIB_PROTOCOL_IP6); + fib_table = fib_table_get_or_null (fib_index, FIB_PROTOCOL_IP6); - if (!(fib_table->ft_flags & FIB_TABLE_FLAG_IP6_LL)) + if (NULL == fib_table || !(fib_table->ft_flags & FIB_TABLE_FLAG_IP6_LL)) continue; ip6_fib_table_show (vm, fib_table, !verbose); diff --git a/src/vnet/ip/lookup.c b/src/vnet/ip/lookup.c index 57db3deed401..70817dbe6e05 100644 --- a/src/vnet/ip/lookup.c +++ b/src/vnet/ip/lookup.c @@ -500,7 +500,13 @@ vnet_show_ip_table_cmd (vlib_main_t *vm, unformat_input_t *main_input, goto done; } - fib = fib_table_get (fib_index, fproto); + fib = fib_table_get_or_null (fib_index, fproto); + if (NULL == fib) + { + error = clib_error_return (0, "table with table_id %u no longer exists", table_id); + goto done; + } + vlib_cli_output (vm, "[%u] table_id:%u %v", fib->ft_index, fib->ft_table_id, fib->ft_desc); } diff --git a/src/vnet/ipsec/ipsec_tun.c b/src/vnet/ipsec/ipsec_tun.c index b2d4227bad3b..25498129e89a 100644 --- a/src/vnet/ipsec/ipsec_tun.c +++ b/src/vnet/ipsec/ipsec_tun.c @@ -557,10 +557,17 @@ ipsec_tun_protect_update (u32 sw_if_index, const ip_address_t * nh, u32 sa_out, u32 * sas_in) { ipsec_tun_protect_t *itp; + ipsec_tun_protect_t *itp_new; u32 itpi, ii, *saip; + u32 n_sa_in_locked; ipsec_main_t *im; + u8 sa_out_locked; int rv; + itp_new = 0; + n_sa_in_locked = 0; + sa_out_locked = 0; + if (NULL == nh) nh = &IP_ADDR_ALL_0; @@ -584,10 +591,12 @@ ipsec_tun_protect_update (u32 sw_if_index, if (~0 == sas_in[ii]) { rv = VNET_API_ERROR_INVALID_VALUE; + n_sa_in_locked = ii; goto out; } } + n_sa_in_locked = vec_len (sas_in); sa_out = ipsec_sa_find_and_lock (sa_out); if (~0 == sa_out) @@ -596,6 +605,8 @@ ipsec_tun_protect_update (u32 sw_if_index, goto out; } + sa_out_locked = 1; + if (INDEX_INVALID == itpi) { vnet_device_class_t *dev_class; @@ -614,6 +625,7 @@ ipsec_tun_protect_update (u32 sw_if_index, } pool_get_zero (ipsec_tun_protect_pool, itp); + itp_new = itp; itp->itp_sw_if_index = sw_if_index; itp->itp_ai = ADJ_INDEX_INVALID; @@ -698,8 +710,29 @@ ipsec_tun_protect_update (u32 sw_if_index, ipsec_sa_unlock (sa_out); vec_foreach (saip, sas_in) ipsec_sa_unlock (*saip); vec_free (sas_in); + return (rv); out: + /* + * Nothing was configured, so this call may not keep what it took: a still + * locked SA can never be deleted again, and a pool entry that never + * reached the database shows up in the walk that lists the protections. + * Only the first n_sa_in_locked entries of sas_in hold locked SA indices; + * the entries after those are still the ids the caller passed, and the + * caller keeps ownership of the vector on the error paths. + */ + if (0 != itp_new) + { + clib_mem_free (itp_new->itp_key); + pool_put (ipsec_tun_protect_pool, itp_new); + } + + for (ii = 0; ii < n_sa_in_locked; ii++) + ipsec_sa_unlock (sas_in[ii]); + + if (sa_out_locked) + ipsec_sa_unlock (sa_out); + return (rv); } diff --git a/src/vnet/session/segment_manager.c b/src/vnet/session/segment_manager.c index 8ddd38d7ca33..ef6c5d33e6fe 100644 --- a/src/vnet/session/segment_manager.c +++ b/src/vnet/session/segment_manager.c @@ -965,21 +965,24 @@ segment_manager_detach_fifo (svm_fifo_t **f) segment_manager_segment_reader_unlock (sm); } -void +int segment_manager_attach_fifo (svm_fifo_t **f, session_t *s) { segment_manager_t *sm; fifo_segment_t *fs; + int rv; sm = segment_manager_get_if_valid ((*f)->segment_manager); if (PREDICT_FALSE (!sm)) - return; + return -1; fs = segment_manager_get_segment_w_lock (sm, (*f)->segment_index); - fifo_segment_attach_fifo (fs, f, s->thread_index); + rv = fifo_segment_attach_fifo (fs, f, s->thread_index); segment_manager_segment_reader_unlock (sm); - (*f)->vpp_sh = s->handle; + if (rv == 0) + (*f)->vpp_sh = s->handle; + return rv; } u32 @@ -1465,6 +1468,7 @@ sm_lookup_free_custom_segment (segment_manager_t *sm, u32 seg_ctx_index) seg_ctx = pool_elt_at_index (smm->custom_seg_ctxs, seg_ctx_index); max_free_bytes = seg_ctx->fifo_pair_bytes; + segment_manager_segment_reader_lock (sm); pool_foreach (ct_seg, seg_ctx->segments) { /* Client or server has detached so segment cannot be used */ @@ -1480,6 +1484,7 @@ sm_lookup_free_custom_segment (segment_manager_t *sm, u32 seg_ctx_index) res = ct_seg; } } + segment_manager_segment_reader_unlock (sm); return res; } diff --git a/src/vnet/session/segment_manager.h b/src/vnet/session/segment_manager.h index f53459a17dbc..7f962327ed05 100644 --- a/src/vnet/session/segment_manager.h +++ b/src/vnet/session/segment_manager.h @@ -145,7 +145,7 @@ int segment_manager_alloc_session_fifos_ct (session_t *s, void segment_manager_dealloc_fifos (svm_fifo_t *rx_fifo, svm_fifo_t *tx_fifo); void segment_manager_dealloc_fifos_ct (svm_fifo_t *rx_fifo, svm_fifo_t *tx_fifo, u32 is_client); void segment_manager_detach_fifo (svm_fifo_t **f); -void segment_manager_attach_fifo (svm_fifo_t **f, session_t *s); +int segment_manager_attach_fifo (svm_fifo_t **f, session_t *s); void segment_manager_set_watermarks (segment_manager_t * sm, u8 high_watermark, u8 low_watermark); diff --git a/src/vnet/session/session.c b/src/vnet/session/session.c index 80e57a78a6b2..942c74c5cb5b 100644 --- a/src/vnet/session/session.c +++ b/src/vnet/session/session.c @@ -923,6 +923,7 @@ session_dgram_connect_notify (transport_connection_t *tc, session_handle_tu_t osh, session_t **new_session) { session_t *new_s; + int rx_attached = 0; /* * Clone half-open session to the right thread. @@ -939,8 +940,11 @@ session_dgram_connect_notify (transport_connection_t *tc, if (!(new_s->flags & SESSION_F_PROXY)) { /* New set of fifos attached to the same shared memory */ - segment_manager_attach_fifo (&new_s->rx_fifo, new_s); - segment_manager_attach_fifo (&new_s->tx_fifo, new_s); + if (segment_manager_attach_fifo (&new_s->rx_fifo, new_s) < 0) + goto attach_error; + rx_attached = 1; + if (segment_manager_attach_fifo (&new_s->tx_fifo, new_s) < 0) + goto attach_error; } /* @@ -954,6 +958,16 @@ session_dgram_connect_notify (transport_connection_t *tc, new_s->connection_index = tc->c_index; *new_session = new_s; return 0; + +attach_error: + if (rx_attached) + segment_manager_detach_fifo (&new_s->rx_fifo); + /* The entry was pointed at new_s above: give it back to the session that + * stays in use */ + if (!(tc->flags & TRANSPORT_CONNECTION_F_NO_LOOKUP)) + session_lookup_add_connection (tc, osh.handle); + session_free (new_s); + return -1; } /** diff --git a/src/vnet/sfdp/sfdp.c b/src/vnet/sfdp/sfdp.c index 9a5173c91b7e..68fb66953a8f 100644 --- a/src/vnet/sfdp/sfdp.c +++ b/src/vnet/sfdp/sfdp.c @@ -336,8 +336,7 @@ sfdp_set_services (sfdp_main_t *sfdp, u32 tenant_id, sfdp_bitmap_t bitmap, clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 }; sfdp_tenant_t *tenant; if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv)) - return clib_error_return ( - 0, "Can't assign service map: tenant id %d not found", tenant_id); + return clib_error_return (0, "Can't assign service map: tenant id %u not found", tenant_id); tenant = sfdp_tenant_at_index (sfdp, kv.value); tenant->bitmaps[direction] = bitmap; @@ -352,11 +351,10 @@ sfdp_set_timeout (sfdp_main_t *sfdp, u32 tenant_id, u32 timeout_idx, clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 }; sfdp_tenant_t *tenant; if (timeout_idx >= SFDP_N_REGISTERED_TIMEOUTS) - return clib_error_return (0, "Can't configure timeout: timeout index %d outside valid range", + return clib_error_return (0, "Can't configure timeout: timeout index %u outside valid range", timeout_idx); if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv)) - return clib_error_return ( - 0, "Can't configure timeout: tenant id %d not found", tenant_id); + return clib_error_return (0, "Can't configure timeout: tenant id %u not found", tenant_id); tenant = sfdp_tenant_at_index (sfdp, kv.value); tenant->timeouts[timeout_idx] = timeout_val; return 0; @@ -370,8 +368,8 @@ sfdp_set_sp_node (sfdp_main_t *sfdp, u32 tenant_id, u32 sp_index, clib_bihash_kv_8_8_t kv = { .key = tenant_id, .value = 0 }; sfdp_tenant_t *tenant; if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv)) - return clib_error_return ( - 0, "Can't configure slow path node: tenant id %d not found", tenant_id); + return clib_error_return (0, "Can't configure slow path node: tenant id %u not found", + tenant_id); tenant = sfdp_tenant_at_index (sfdp, kv.value); tenant->sp_node_indices[sp_index] = node_index; return 0; @@ -387,8 +385,8 @@ sfdp_set_icmp_error_node (sfdp_main_t *sfdp, u32 tenant_id, u8 is_ip6, sfdp_tenant_t *tenant; uword next_index; if (clib_bihash_search_inline_8_8 (&sfdp->tenant_idx_by_id, &kv)) - return clib_error_return ( - 0, "Can't configure icmp error node: tenant id %d not found", tenant_id); + return clib_error_return (0, "Can't configure icmp error node: tenant id %u not found", + tenant_id); tenant = sfdp_tenant_at_index (sfdp, kv.value); if (is_ip6) { diff --git a/src/vnet/span/span_api.c b/src/vnet/span/span_api.c index 7bfb6f313902..e783a9c1f0aa 100644 --- a/src/vnet/span/span_api.c +++ b/src/vnet/span/span_api.c @@ -25,14 +25,23 @@ static void (vl_api_sw_interface_span_enable_disable_t * mp) { vl_api_sw_interface_span_enable_disable_reply_t *rmp; - int rv; + int rv = 0; vlib_main_t *vm = vlib_get_main (); + if (!vnet_sw_if_index_is_api_valid (ntohl (mp->sw_if_index_from)) || + (ntohl (mp->sw_if_index_to) != ~0 && + !vnet_sw_if_index_is_api_valid (ntohl (mp->sw_if_index_to)))) + { + rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; + goto bad_sw_if_index; + } + rv = span_add_delete_entry (vm, ntohl (mp->sw_if_index_from), ntohl (mp->sw_if_index_to), ntohl (mp->state), mp->is_l2 ? SPAN_FEAT_L2 : SPAN_FEAT_DEVICE); + BAD_SW_IF_INDEX_LABEL; REPLY_MACRO (VL_API_SW_INTERFACE_SPAN_ENABLE_DISABLE_REPLY); } diff --git a/src/vnet/tls/tls.c b/src/vnet/tls/tls.c index 910d47a9c779..c26ab27d6d48 100644 --- a/src/vnet/tls/tls.c +++ b/src/vnet/tls/tls.c @@ -1313,7 +1313,12 @@ dtls_connect (transport_endpoint_cfg_t *tep) cargs->sep_ext.ns_index = app->ns_index; cargs->sep_ext.transport_flags = TRANSPORT_CFG_F_CONNECTED; if ((rv = vnet_connect (cargs))) - return rv; + { + tls_ctx_half_open_free (ctx_handle); + return rv; + } + + ctx->tls_session_handle = cargs->sh; TLS_DBG (1, "New DTLS connect request %x engine %d", ctx_handle, engine_type); diff --git a/src/vnet/udp/udp_input.c b/src/vnet/udp/udp_input.c index a0bee693481b..a96647f7e430 100644 --- a/src/vnet/udp/udp_input.c +++ b/src/vnet/udp/udp_input.c @@ -324,7 +324,24 @@ udp46_input_inline (vlib_main_t *vm, vlib_node_runtime_t *node, /* * Ask session layer for a new session. */ - session_dgram_connect_notify (&uc0->connection, osh, &s0); + if (PREDICT_FALSE (session_dgram_connect_notify ( + &uc0->connection, osh, &s0) < 0)) + { + /* + * The migration was rolled back and s0 still belongs to + * another thread. Undo the transport clone, hand the + * datagram to the session's own thread and let it notify + * the application. + */ + udp_connection_t *old_uc = udp_connection_from_transport ( + session_get_transport (s0)); + old_uc->flags &= ~UDP_CONN_F_MIGRATED; + udp_connection_enqueue (uc0, s0, &hdr0, thread_index, b[0], + 0 /* queue_event */, &error0); + session_program_rx_io_evt (osh); + udp_connection_free (uc0); + goto done; + } queue_event = 0; } else diff --git a/src/vpp-api/python/vpp_papi/tests/test_vapi_af.py b/src/vpp-api/python/vpp_papi/tests/test_vapi_af.py new file mode 100644 index 000000000000..e9d41ed1c221 --- /dev/null +++ b/src/vpp-api/python/vpp_papi/tests/test_vapi_af.py @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2026 Bo Xu + +import ipaddress +import unittest + +from vpp_papi import vpp_papi +from vpp_papi import vpp_papi_async + +JSON_API = """\ +{ + "enums": [ + [ + "address_family", + [ + "ADDRESS_IP4", + 0 + ], + [ + "ADDRESS_IP6", + 1 + ], + { + "enumtype": "u8" + } + ] + ] +} +""" + + +class TestVapiAf(unittest.TestCase): + """The vapi_af and vapi_af_name properties of an IP address.""" + + def setUp(self): + # The properties resolve the address_family enum through the type + # table, so make sure that enum is known. + vpp_papi.VPPApiJSONFiles().process_json_str(JSON_API) + + def test_vapi_af_and_name_of_ip_address(self): + af = vpp_papi.VppEnum.vl_api_address_family_t + + # Both modules install the properties on the same base class, so + # install one at a time to exercise both implementations. + for module in (vpp_papi, vpp_papi_async): + with self.subTest(module=module.__name__): + module.add_convenience_methods() + ip4 = ipaddress.ip_address("1.2.3.4") + ip6 = ipaddress.ip_address("::1") + self.assertEqual(af.ADDRESS_IP4.value, ip4.vapi_af) + self.assertEqual(af.ADDRESS_IP6.value, ip6.vapi_af) + self.assertEqual("ip4", ip4.vapi_af_name) + self.assertEqual("ip6", ip6.vapi_af_name) + + def test_vapi_af_rejects_other_versions(self): + class NotAnIpAddress(ipaddress.IPv4Address): + version = 5 + + for module in (vpp_papi, vpp_papi_async): + with self.subTest(module=module.__name__): + module.add_convenience_methods() + addr = NotAnIpAddress("1.2.3.4") + for attr in ("vapi_af", "vapi_af_name"): + with self.assertRaises(ValueError) as cm: + getattr(addr, attr) + self.assertIn("Invalid version", str(cm.exception)) diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi/vpp_papi.py index 53e830ea4eb4..a7bac294d78f 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi/vpp_papi.py @@ -99,18 +99,18 @@ def vpp_atexit(vpp_weakref): def add_convenience_methods(): # provide convenience methods to IP[46]Address.vapi_af def _vapi_af(self): - if 6 == self._version: + if 6 == self.version: return VppEnum.vl_api_address_family_t.ADDRESS_IP6.value - if 4 == self._version: + if 4 == self.version: return VppEnum.vl_api_address_family_t.ADDRESS_IP4.value - raise ValueError("Invalid _version.") + raise ValueError("Invalid version.") def _vapi_af_name(self): - if 6 == self._version: + if 6 == self.version: return "ip6" - if 4 == self._version: + if 4 == self.version: return "ip4" - raise ValueError("Invalid _version.") + raise ValueError("Invalid version.") ipaddress._IPAddressBase.vapi_af = property(_vapi_af) ipaddress._IPAddressBase.vapi_af_name = property(_vapi_af_name) diff --git a/src/vpp-api/python/vpp_papi/vpp_papi_async.py b/src/vpp-api/python/vpp_papi/vpp_papi_async.py index c8a51896a7ce..3ae73ddfc043 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi_async.py +++ b/src/vpp-api/python/vpp_papi/vpp_papi_async.py @@ -88,18 +88,18 @@ def vpp_atexit(vpp_weakref): def add_convenience_methods(): # provide convenience methods to IP[46]Address.vapi_af def _vapi_af(self): - if 6 == self._version: + if 6 == self.version: return VppEnum.vl_api_address_family_t.ADDRESS_IP6.value - if 4 == self._version: + if 4 == self.version: return VppEnum.vl_api_address_family_t.ADDRESS_IP4.value - raise ValueError("Invalid _version.") + raise ValueError("Invalid version.") def _vapi_af_name(self): - if 6 == self._version: + if 6 == self.version: return "ip6" - if 4 == self._version: + if 4 == self.version: return "ip4" - raise ValueError("Invalid _version.") + raise ValueError("Invalid version.") ipaddress._IPAddressBase.vapi_af = property(_vapi_af) ipaddress._IPAddressBase.vapi_af_name = property(_vapi_af_name) diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c index 1fbe0e105568..bc6e4fa8157b 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -55,6 +55,7 @@ typedef struct } vapi_req_t; static const u32 context_counter_mask = (1 << 31); +static const u32 vapi_sock_max_msg_size = 64 << 20; typedef struct { @@ -516,6 +517,12 @@ vapi_sock_recv_internal (vapi_ctx_t ctx, u8 **vec_msg, u32 timeout) mbp = (msgbuf_t *) (sock->rx_buffer); data_len = ntohl (mbp->data_len); + if (data_len > vapi_sock_max_msg_size || + data_len > UINT32_MAX - sizeof (*mbp)) + { + vec_reset_length (sock->rx_buffer); + return VAPI_EINVAL; + } current_rx_index = vec_len (sock->rx_buffer); vec_validate (sock->rx_buffer, current_rx_index + data_len); mbp = (msgbuf_t *) (sock->rx_buffer); @@ -753,7 +760,8 @@ vapi_sock_client_connect (vapi_ctx_t ctx, char *path, const char *name) if (vapi_sock_send (ctx, (void *) mp) != VAPI_OK) { - return VAPI_ECON_FAIL; + rv = VAPI_ECON_FAIL; + goto fail; } while (1) @@ -771,7 +779,10 @@ vapi_sock_client_connect (vapi_ctx_t ctx, char *path, const char *name) goto read_one_msg; if (qstatus != VAPI_EAGAIN) - return VAPI_ECON_FAIL; + { + rv = VAPI_ECON_FAIL; + goto fail; + } ts.tv_sec = 0; ts.tv_nsec = 10000 * 1000; /* 10 ms */ @@ -779,7 +790,8 @@ vapi_sock_client_connect (vapi_ctx_t ctx, char *path, const char *name) ts = tsrem; } /* Timeout... */ - return VAPI_ECON_FAIL; + rv = VAPI_ECON_FAIL; + goto fail; read_one_msg: if (vec_len (msg) == 0) @@ -796,6 +808,12 @@ vapi_sock_client_connect (vapi_ctx_t ctx, char *path, const char *name) break; } return (rv); + +fail: + clib_socket_close (sock); + clib_socket_free (sock); + vec_free (msg); + return rv; } static void @@ -887,7 +905,6 @@ vapi_sock_client_disconnect (vapi_ctx_t ctx) { vl_api_sockclnt_delete_reply_t *rp; u8 *msg = 0; - msgbuf_t *msgbuf; int rv; f64 deadline; @@ -911,18 +928,15 @@ vapi_sock_client_disconnect (vapi_ctx_t ctx) if (vapi_sock_recv_internal (ctx, &msg, 0) != VAPI_OK) continue; - msgbuf = (void *) msg; - rp = (void *) msgbuf->data; + rp = (void *) msg; /* drain the queue */ if (ntohs (rp->_vl_msg_id) != VL_API_SOCKCLNT_DELETE_REPLY) { clib_warning ("queue drain: %d", ntohs (rp->_vl_msg_id)); - msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); - vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + vec_free (msg); continue; } - msgbuf = (msgbuf_t *) ((u8 *) rp - offsetof (msgbuf_t, data)); - vl_msg_api_handler ((void *) rp, ntohl (msgbuf->data_len)); + vec_free (msg); break; } diff --git a/src/vpp/app/vppctl.c b/src/vpp/app/vppctl.c index 6a981d69b67d..8ce574ef3dac 100644 --- a/src/vpp/app/vppctl.c +++ b/src/vpp/app/vppctl.c @@ -112,6 +112,9 @@ process_input (int sock_fd, unsigned char *rx_buf, int rx_buf_len, { if (rx_buf[i] == IAC) { + /* a truncated IAC negotiation needs 2 bytes past i; don't overrun */ + if (i + 2 >= rx_buf_len) + break; if (rx_buf[i + 1] == SB) { char opt = rx_buf[i + 2]; diff --git a/src/vppinfra/linux/mem.c b/src/vppinfra/linux/mem.c index ce2334288193..7a3dd058c380 100644 --- a/src/vppinfra/linux/mem.c +++ b/src/vppinfra/linux/mem.c @@ -311,6 +311,7 @@ clib_mem_vm_map_internal (void *base, clib_mem_page_sz_t log2_page_sz, { clib_mem_main_t *mm = &clib_mem_main; clib_mem_vm_map_hdr_t *hdr; + void *reserved_base; uword sys_page_sz = 1ULL << mm->log2_page_sz; int mmap_flags = MAP_FIXED, is_huge = 0; @@ -353,21 +354,21 @@ clib_mem_vm_map_internal (void *base, clib_mem_page_sz_t log2_page_sz, size = round_pow2 (size, 1ULL << log2_page_sz); - base = (void *) clib_mem_vm_reserve ((uword) base, size, - clib_max (log2_page_sz, log2_align)); + reserved_base = (void *) clib_mem_vm_reserve ( + (uword) base, size, clib_max (log2_page_sz, log2_align)); - if (base == (void *) ~0) + if (reserved_base == (void *) ~0) return CLIB_MEM_VM_MAP_FAILED; - base = mmap (base, size, PROT_READ | PROT_WRITE, mmap_flags, fd, offset); + base = mmap (reserved_base, size, PROT_READ | PROT_WRITE, mmap_flags, fd, + offset); if (base == MAP_FAILED) - return CLIB_MEM_VM_MAP_FAILED; + goto error; if (is_huge && (mlock (base, size) != 0)) { - munmap (base, size); - return CLIB_MEM_VM_MAP_FAILED; + goto error; } hdr = mmap (base - sys_page_sz, sys_page_sz, PROT_READ | PROT_WRITE, @@ -375,8 +376,7 @@ clib_mem_vm_map_internal (void *base, clib_mem_page_sz_t log2_page_sz, if (hdr != base - sys_page_sz) { - munmap (base, size); - return CLIB_MEM_VM_MAP_FAILED; + goto error; } map_lock (); @@ -408,6 +408,10 @@ clib_mem_vm_map_internal (void *base, clib_mem_page_sz_t log2_page_sz, clib_mem_unpoison (base, size); return base; + +error: + munmap (reserved_base - sys_page_sz, size + sys_page_sz); + return CLIB_MEM_VM_MAP_FAILED; } __clib_export int diff --git a/src/vppinfra/mem_dlmalloc.c b/src/vppinfra/mem_dlmalloc.c index 40a707b595f2..60e06ce872df 100644 --- a/src/vppinfra/mem_dlmalloc.c +++ b/src/vppinfra/mem_dlmalloc.c @@ -79,11 +79,24 @@ clib_mem_create_heap_internal (void *base, uword size, h.size = size; h.log2_page_sz = log2_page_sz; h.mspace = create_mspace_with_base (base, size, is_locked); + if (h.mspace == 0) + { + if (h.unmap_on_destroy) + clib_mem_vm_unmap (base); + return 0; + } mspace_disable_expand (h.mspace); clib_mem_poison (mspace_least_addr (h.mspace), mspace_footprint (h.mspace)); sz = round_pow2 (sizeof (clib_mem_heap_t) + strlen (name) + 1, align); hp = mspace_memalign (h.mspace, align, sz); + if (hp == 0) + { + destroy_mspace (h.mspace); + if (h.unmap_on_destroy) + clib_mem_vm_unmap (base); + return 0; + } clib_mem_unpoison (hp, sz); *hp = h; diff --git a/src/vppinfra/test_tw_timer.c b/src/vppinfra/test_tw_timer.c index 72a7f45d39f6..13ecf4f589ba 100644 --- a/src/vppinfra/test_tw_timer.c +++ b/src/vppinfra/test_tw_timer.c @@ -54,6 +54,14 @@ typedef struct tw_timer_test_main_t tw_timer_test_main; +static u32 safety_expired_count; + +static void +expired_timer_safety_callback (u32 * expired_timers) +{ + safety_expired_count += vec_len (expired_timers); +} + static void run_single_wheel (tw_timer_wheel_2t_1w_2048sl_t * tw, u32 n_ticks) { @@ -1318,6 +1326,60 @@ test5_double (tw_timer_test_main_t * tm) return 0; } +static clib_error_t * +test6_safety (tw_timer_test_main_t * tm) +{ + tw_timer_wheel_2t_1w_2048sl_t tw; + f64 first_run, large_gap; + + (void) tm; + + tw_timer_wheel_init_2t_1w_2048sl (&tw, expired_timer_safety_callback, 1.0, + 1 /* max expirations */); + + /* Prime the wheel, then make a gap much larger than the wheel geometry. */ + tw_timer_expire_timers_2t_1w_2048sl (&tw, 1.0); + first_run = tw.last_run_time; + large_gap = first_run + 1e10; + tw_timer_expire_timers_2t_1w_2048sl (&tw, large_gap); + + if (tw.last_run_time != large_gap) + { + tw_timer_wheel_free_2t_1w_2048sl (&tw); + return clib_error_return (0, "empty wheel did not re-anchor after large gap"); + } + + safety_expired_count = 0; + tw_timer_start_2t_1w_2048sl (&tw, 0, 0, 1); + tw_timer_start_2t_1w_2048sl (&tw, 1, 0, 1); + tw_timer_expire_timers_2t_1w_2048sl (&tw, large_gap + 2.0); + + if (safety_expired_count != 1) + { + tw_timer_wheel_free_2t_1w_2048sl (&tw); + return clib_error_return (0, "max_expirations was not enforced: %u", + safety_expired_count); + } + + if (tw.last_run_time >= large_gap + 2.0) + { + tw_timer_wheel_free_2t_1w_2048sl (&tw); + return clib_error_return (0, "active timer backlog was discarded"); + } + + tw_timer_expire_timers_2t_1w_2048sl (&tw, large_gap + 2.0); + + if (safety_expired_count != 2) + { + tw_timer_wheel_free_2t_1w_2048sl (&tw); + return clib_error_return (0, "same-slot backlog was not preserved: %u", + safety_expired_count); + } + + tw_timer_wheel_free_2t_1w_2048sl (&tw); + return 0; +} + static clib_error_t * timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input) { @@ -1328,6 +1390,7 @@ timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input) int is_test3 = 0; int is_test4 = 0; int is_test5 = 0; + int is_test6 = 0; int overflow = 0; clib_memset (tm, 0, sizeof (*tm)); @@ -1353,6 +1416,8 @@ timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input) is_test4 = 1; else if (unformat (input, "linear")) is_test5 = 1; + else if (unformat (input, "safety")) + is_test6 = 1; else if (unformat (input, "updates")) is_updates = 1; else if (unformat (input, "wheels %d", &num_wheels)) @@ -1367,7 +1432,7 @@ timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input) break; } - if (is_test1 + is_test2 + is_test3 + is_test4 + is_test5 == 0) + if (is_test1 + is_test2 + is_test3 + is_test4 + is_test5 + is_test6 == 0) return clib_error_return (0, "No test specified [test1..n]"); if (num_wheels < 1 || num_wheels > 3) @@ -1409,6 +1474,9 @@ timer_test_command_fn (tw_timer_test_main_t * tm, unformat_input_t * input) if (is_test5) return test5_double (tm); + if (is_test6) + return test6_safety (tm); + /* NOTREACHED */ return 0; } diff --git a/src/vppinfra/time.c b/src/vppinfra/time.c index 36e9a4fa11fb..8a88f7f369d3 100644 --- a/src/vppinfra/time.c +++ b/src/vppinfra/time.c @@ -218,6 +218,7 @@ clib_time_verify_frequency (clib_time_t * c) { f64 now_reference, delta_reference, delta_reference_max; f64 delta_clock_in_seconds; + f64 elapsed_time; u64 now_clock, delta_clock; f64 new_clocks_per_second, delta; @@ -230,7 +231,8 @@ clib_time_verify_frequency (clib_time_t * c) * lower TSC). In this case, skip frequency estimation and just resync * timestamps to current values. */ - if (PREDICT_FALSE (now_clock < c->last_verify_cpu_time)) + if (PREDICT_FALSE (now_clock < c->last_verify_cpu_time || + now_clock < c->last_cpu_time)) { c->last_cpu_time = now_clock; c->last_verify_cpu_time = now_clock; @@ -246,15 +248,11 @@ clib_time_verify_frequency (clib_time_t * c) delta_clock_in_seconds = (f64) (now_clock - c->last_verify_cpu_time) * c->seconds_per_clock; - /* - * Recompute vpp start time reference, and total clocks - * using the current clock rate. - * Ensure total_cpu_time never decreases to guarantee monotonicity. - */ + /* Recompute vpp start time reference using the current clock rate. */ c->init_reference_time += (delta_reference - delta_clock_in_seconds); - c->total_cpu_time = clib_max ( - c->total_cpu_time, (u64) ((now_reference - c->init_reference_time) * c->clocks_per_second)); + /* Account for elapsed clocks before advancing the sample baseline. */ + c->total_cpu_time += now_clock - c->last_cpu_time; c->last_cpu_time = now_clock; /* Calculate a new clock rate sample */ @@ -295,18 +293,16 @@ clib_time_verify_frequency (clib_time_t * c) return; } + elapsed_time = c->total_cpu_time * c->seconds_per_clock; + /* Add sample to the exponentially-smoothed rate */ c->clocks_per_second = c->clocks_per_second * c->damping_constant + (1.0 - c->damping_constant) * new_clocks_per_second; c->seconds_per_clock = 1.0 / c->clocks_per_second; - /* - * Recalculate total_cpu_time based on the kernel timebase, and - * the calculated clock rate. - * Ensure total_cpu_time never decreases to guarantee monotonicity. - */ + /* Keep reported elapsed time continuous across the frequency change. */ c->total_cpu_time = clib_max ( - c->total_cpu_time, (u64) ((now_reference - c->init_reference_time) * c->clocks_per_second)); + c->total_cpu_time, (u64) (elapsed_time * c->clocks_per_second)); } diff --git a/src/vppinfra/tw_timer_template.c b/src/vppinfra/tw_timer_template.c index 6bedc4de92e6..8e11e51c4209 100644 --- a/src/vppinfra/tw_timer_template.c +++ b/src/vppinfra/tw_timer_template.c @@ -495,7 +495,8 @@ static inline f64 now, u32 * callback_vector_arg) { - u32 nticks, i; + u32 nticks, i, ticks_processed = 0, expirations_processed = 0; + f64 elapsed_ticks; tw_timer_wheel_slot_t *ts; TWT (tw_timer) * t, *head; u32 *callback_vector; @@ -508,22 +509,39 @@ static inline if (PREDICT_FALSE (now < tw->next_run_time)) return callback_vector_arg; - /* Number of ticks which have occurred */ - nticks = tw->ticks_per_second * (now - tw->last_run_time); - if (nticks == 0) - return callback_vector_arg; - - /* Remember when we ran, compute next runtime */ - tw->next_run_time = (now + tw->timer_interval); - /* First call, or time jumped backwards? */ if (PREDICT_FALSE ((tw->last_run_time == 0.0) || (now <= tw->last_run_time))) { tw->last_run_time = now; + tw->next_run_time = now + tw->timer_interval; return callback_vector_arg; } + /* An empty wheel has no expiration backlog to preserve. */ + if (pool_elts (tw->timers) == TW_TIMER_WHEELS * TW_SLOTS_PER_RING +#if TW_OVERFLOW_VECTOR > 0 + + 1 +#endif + ) + { + tw->last_run_time = now; + tw->next_run_time = now + tw->timer_interval; + return callback_vector_arg; + } + + /* Number of ticks which have occurred. Validate before converting to u32. */ + elapsed_ticks = tw->ticks_per_second * (now - tw->last_run_time); + if (!(elapsed_ticks > 0.0)) + return callback_vector_arg; + + if (elapsed_ticks >= (f64) (2 * TW_SLOTS_PER_RING)) + nticks = 2 * TW_SLOTS_PER_RING; + else + nticks = (u32) elapsed_ticks; + if (nticks == 0) + return callback_vector_arg; + if (callback_vector_arg == 0) { vec_set_len (tw->expired_timer_handles, 0); @@ -554,17 +572,13 @@ static inline head = pool_elt_at_index (tw->timers, ts->head_index); next_index = head->next; - /* Make slot empty */ - head->next = head->prev = ts->head_index; - /* traverse slot, place timers wherever they go */ while (next_index != head - tw->timers) { t = pool_elt_at_index (tw->timers, next_index); next_index = t->next; - /* Remove from the overflow vector (hammer) */ - t->next = t->prev = ~0; + timer_remove (tw->timers, t); ASSERT (t->expiration_time >= tw->current_tick); @@ -589,12 +603,19 @@ static inline t->slow_ring_offset = new_slow_ring_offset; t->fast_ring_offset = new_fast_ring_offset; - /* Timer expires Right Now */ - if (PREDICT_FALSE (t->slow_ring_offset == 0 && - t->fast_ring_offset == 0 && - new_glacier_ring_offset == 0)) - { - vec_add1 (callback_vector, t->user_handle); + /* Timer expires Right Now */ + if (PREDICT_FALSE (t->slow_ring_offset == 0 && + t->fast_ring_offset == 0 && + new_glacier_ring_offset == 0)) + { + if (expirations_processed + vec_len (callback_vector) >= + tw->max_expirations) + { + ts = &tw->w[TW_TIMER_RING_FAST][0]; + timer_addhead (tw->timers, ts->head_index, t - tw->timers); + break; + } + vec_add1 (callback_vector, t->user_handle); #if TW_START_STOP_TRACE_SIZE > 0 TW (tw_timer_trace) (tw, 0xfe, t->user_handle, t - tw->timers); @@ -642,23 +663,26 @@ static inline head = pool_elt_at_index (tw->timers, ts->head_index); next_index = head->next; - /* Make slot empty */ - head->next = head->prev = ts->head_index; - /* traverse slot, deal timers into slow ring */ while (next_index != head - tw->timers) { t = pool_elt_at_index (tw->timers, next_index); next_index = t->next; - /* Remove from glacier ring slot (hammer) */ - t->next = t->prev = ~0; - - /* Timer expires Right Now */ - if (PREDICT_FALSE (t->slow_ring_offset == 0 && - t->fast_ring_offset == 0)) - { - vec_add1 (callback_vector, t->user_handle); + timer_remove (tw->timers, t); + + /* Timer expires Right Now */ + if (PREDICT_FALSE (t->slow_ring_offset == 0 && + t->fast_ring_offset == 0)) + { + if (expirations_processed + vec_len (callback_vector) >= + tw->max_expirations) + { + ts = &tw->w[TW_TIMER_RING_FAST][0]; + timer_addhead (tw->timers, ts->head_index, t - tw->timers); + break; + } + vec_add1 (callback_vector, t->user_handle); #if TW_START_STOP_TRACE_SIZE > 0 TW (tw_timer_trace) (tw, 0xfe, t->user_handle, t - tw->timers); @@ -698,22 +722,25 @@ static inline head = pool_elt_at_index (tw->timers, ts->head_index); next_index = head->next; - /* Make slot empty */ - head->next = head->prev = ts->head_index; - /* traverse slot, deal timers into fast ring */ while (next_index != head - tw->timers) { t = pool_elt_at_index (tw->timers, next_index); next_index = t->next; - /* Remove from sloe ring slot (hammer) */ - t->next = t->prev = ~0; - - /* Timer expires Right Now */ - if (PREDICT_FALSE (t->fast_ring_offset == 0)) - { - vec_add1 (callback_vector, t->user_handle); + timer_remove (tw->timers, t); + + /* Timer expires Right Now */ + if (PREDICT_FALSE (t->fast_ring_offset == 0)) + { + if (expirations_processed + vec_len (callback_vector) >= + tw->max_expirations) + { + ts = &tw->w[TW_TIMER_RING_FAST][0]; + timer_addhead (tw->timers, ts->head_index, t - tw->timers); + break; + } + vec_add1 (callback_vector, t->user_handle); #if TW_START_STOP_TRACE_SIZE > 0 TW (tw_timer_trace) (tw, 0xfe, t->user_handle, t - tw->timers); @@ -742,14 +769,14 @@ static inline head = pool_elt_at_index (tw->timers, ts->head_index); next_index = head->next; - /* Make slot empty */ - head->next = head->prev = ts->head_index; - /* Construct vector of expired timer handles to give the user */ - while (next_index != ts->head_index) + while (next_index != ts->head_index && + expirations_processed + vec_len (callback_vector) < + tw->max_expirations) { t = pool_elt_at_index (tw->timers, next_index); next_index = t->next; + timer_remove (tw->timers, t); vec_add1 (callback_vector, t->user_handle); #if TW_START_STOP_TRACE_SIZE > 0 TW (tw_timer_trace) (tw, 0xfe, t->user_handle, t - tw->timers); @@ -763,12 +790,17 @@ static inline /* The callback is optional. We return the u32 * handle vector */ if (tw->expired_timer_callback) { + expirations_processed += vec_len (callback_vector); tw->expired_timer_callback (callback_vector); vec_reset_length (callback_vector); } tw->expired_timer_handles = callback_vector; } + /* Do not advance past a partially drained slot. */ + if (head->next != ts->head_index) + break; + #if TW_FAST_WHEEL_BITMAP tw->fast_slot_bitmap = clib_bitmap_set (tw->fast_slot_bitmap, fast_wheel_index, 0); @@ -790,14 +822,19 @@ static inline tw->current_index[TW_TIMER_RING_GLACIER] = glacier_wheel_index; #endif - if (vec_len (callback_vector) >= tw->max_expirations) + ticks_processed++; + + if (expirations_processed + vec_len (callback_vector) >= + tw->max_expirations) break; } if (callback_vector_arg == 0) tw->expired_timer_handles = callback_vector; - tw->last_run_time += i * tw->timer_interval; + /* Keep elapsed time as backlog while active timers remain. */ + tw->last_run_time += ticks_processed * tw->timer_interval; + tw->next_run_time = tw->last_run_time + tw->timer_interval; return callback_vector; } diff --git a/test/asf/test_api_trace.py b/test/asf/test_api_trace.py index 04fcf63ca411..1f997d258eb2 100644 --- a/test/asf/test_api_trace.py +++ b/test/asf/test_api_trace.py @@ -20,17 +20,19 @@ def setUp(self): def tearDownClass(cls): super(TestJsonApiTrace, cls).tearDownClass() - def test_json_api_trace_save(self): - self.vapi.show_version() - + def save_json_trace(self): fname = "test_api_trace-%d.json" % self.vpp.pid tmp_api_trace = "/tmp/%s" % fname fpath = "%s/%s" % (self.tempdir, fname) self.vapi.cli("api trace save-json {}".format(fname)) shutil.move(tmp_api_trace, fpath) with open(fpath, encoding="utf-8") as f: - s = f.read() - trace = json.loads(s) + return json.load(f) + + def test_json_api_trace_save(self): + self.vapi.show_version() + + trace = self.save_json_trace() found = False for o in trace: if o["_msgname"] == "show_version": @@ -39,6 +41,31 @@ def test_json_api_trace_save(self): self.assertTrue(found) self.assertEqual(o["_msgname"], "show_version") + def test_json_api_trace_variable_length_message(self): + """A message with a variable length array is traced and saved whole""" + n_segments = 100 + # The request is traced when it is received, the reply is of no + # interest here, so call the API without the return value check. The + # stored copy has to hold all the segments: a copy cut short keeps the + # element count and the dump walks off the end of it. + self.vapi.papi.sr_mpls_policy_add( + bsid=1, + weight=1, + is_spray=False, + n_segments=n_segments, + segments=list(range(100, 100 + n_segments)), + ) + + trace = self.save_json_trace() + o = [e for e in trace if e["_msgname"] == "sr_mpls_policy_add"][0] + self.assertEqual(o["n_segments"], n_segments) + self.assertEqual(o["segments"], list(range(100, 100 + n_segments))) + + # dumping walks the same arrays as saving + dump = self.vapi.cli("api trace dump-json") + self.assertIn("sr_mpls_policy_add", dump) + self.assertIn("segments", dump) + def test_json_api_trace_replay(self): fname = "/tmp/create_loop.json" req = """ diff --git a/test/test_cnat.py b/test/test_cnat.py index 414d8e451c9d..de293e21815f 100644 --- a/test/test_cnat.py +++ b/test/test_cnat.py @@ -1036,6 +1036,36 @@ def test_cnat_fhc(self): self._make_multi_backend_translations() self.cnat_fhc_translation() + def test_cnat_zero_paths(self): + # """ CNat Translation with an empty path list """ + self.translations = [] + self.mbtranslations = [] + translation = Translation( + self, + TCP, + Endpoint(ip="30.0.0.8", port=8888, is_v6=False), + [], + 0x9F, + ).add_vpp_config() + self.translations.append(translation) + try: + self.assertIsNotNone(translation.id) + dump = translation.query_vpp_config() + self.assertIsNotNone(dump) + self.assertEqual(0, dump.n_paths) + translation_lines = [ + line + for line in self.vapi.cli("show cnat translation").splitlines() + if line.startswith(f"[{translation.id}]") + ] + self.logger.info(translation_lines) + self.assertEqual(1, len(translation_lines)) + self.assertIn("30.0.0.8;8888 TCP", translation_lines[0]) + self.assertIn("lb:default", translation_lines[0]) + finally: + self.translations.remove(translation) + translation.remove_vpp_config() + @unittest.skipIf("cnat" in config.excluded_plugins, "Exclude CNAT plugin tests") class TestCNatTranslationCLI(CnatCommonTestCase): @@ -1080,6 +1110,9 @@ def test_cnat_cli_translation_validation(self): class TestCNatSourceNAT(CnatCommonTestCase): """CNat Source NAT""" + # vnet/error.h: no such FIB / VRF + VNET_API_ERROR_NO_SUCH_FIB = -3 + @classmethod def setUpClass(cls): super(TestCNatSourceNAT, cls).setUpClass() @@ -1402,6 +1435,138 @@ def test_snat_limit(self): self.vapi.cnat_session_purge() + def test_snat_invalid_fib_index(self): + """CNat Source Nat policy for a fib index that has no table""" + snat_ip4 = self.pg2.remote_hosts[0].ip4 + + # Both fib indices of the policy size the per-VRF state, so an index + # that names no table is refused instead of being allocated for. A + # request for ~0 entries used to abort vpp. + with self.vapi.assert_negative_api_retval(): + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0xFFFFFFFF, + ret_fib_index=0, + snat_ip4=snat_ip4, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, self.VNET_API_ERROR_NO_SUCH_FIB) + + # Same for the fib index the return path is installed in. + with self.vapi.assert_negative_api_retval(): + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0, + ret_fib_index=0xFFFFFFFF, + snat_ip4=snat_ip4, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, self.VNET_API_ERROR_NO_SUCH_FIB) + + # vpp is still there and the same call with valid fib indices goes + # through + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0, + ret_fib_index=0, + snat_ip4=snat_ip4, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, 0) + self.assertIn(snat_ip4, self.vapi.cli("show cnat snat-policy")) + + def test_snat_delete_only_named_fib_index(self): + """CNat Source Nat delete only removes the policy its fib index names""" + cli = self.vapi.cli + snat_ip4 = self.pg2.remote_hosts[0].ip4 + + # The lookup answers with the default policy when a fib index names no + # policy of its own, and the update then records that policy under the + # index, so the slots of several indexes end up naming one entry. The + # CLI marks the entry it creates for fib index 0 as the default policy. + self.vapi.cnat_set_snat_addresses(sw_if_index=INVALID_INDEX) + cli("set cnat snat-policy addr %s" % snat_ip4) + self.vapi.ip_table_add_del_v2(is_add=1, table={"table_id": 5}) + cli("set cnat snat-policy addr %s fib 1" % snat_ip4) + + # The delete of the index the entry was created for removes it, and + # every slot that named it has to be empty afterwards instead of naming + # the freed entry, so fib index 1 can be configured again. + reply = self.vapi.cnat_set_snat_addresses( + snat_ip4=None, snat_ip6=None, sw_if_index=INVALID_INDEX + ) + self.assertEqual(reply.retval, 0) + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=1, + ret_fib_index=1, + snat_ip4=snat_ip4, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, 0) + + # A delete names one fib index and must not be answered with the + # policy of another one. + cli("set cnat snat-policy addr %s" % snat_ip4) + with self.vapi.assert_negative_api_retval(): + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0xFFFFFFFF, + ret_fib_index=0, + snat_ip4=None, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, self.VNET_API_ERROR_NO_SUCH_FIB) + self.assertIn(snat_ip4, cli("show cnat snat-policy")) + + def test_snat_refused_delete_keeps_the_watch(self): + """A refused CNat Source Nat delete leaves the policy alone""" + # vnet/cnat/cnat_snat_policy.h: CNAT_SNAT_POLICY_FLAG_USE_AS_DEFAULT + CNAT_SNAT_POLICY_FLAG_USE_AS_DEFAULT = 4 + + # The policy setUp configured holds fib index 0, so it has to go before + # one that is marked as the default policy can take its place. + self.vapi.cnat_set_snat_addresses(sw_if_index=INVALID_INDEX) + + # The policy is created for an interface that carries no address yet, + # so cnat_resolve_addr() fails and the endpoint is watched instead of + # being resolved right away. + loopback = self.create_loopback_interfaces(1)[0] + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0, + ret_fib_index=0, + snat_ip4=None, + snat_ip6=None, + sw_if_index=loopback.sw_if_index, + flags=CNAT_SNAT_POLICY_FLAG_USE_AS_DEFAULT, + ) + self.assertEqual(reply.retval, 0) + + # A delete names a fib index that holds no policy of its own, so the + # lookup answers with the default policy. The call has to be refused, + # and it may not have dropped the watch of that policy on the way. + with self.vapi.assert_negative_api_retval(): + reply = self.vapi.cnat_set_snat_addresses_v2( + fwd_fib_index=0xFFFFFFFF, + ret_fib_index=0, + snat_ip4=None, + snat_ip6=None, + sw_if_index=INVALID_INDEX, + flags=0, + ) + self.assertEqual(reply.retval, self.VNET_API_ERROR_NO_SUCH_FIB) + + # The watch still has to answer the address that appears now. + self.vapi.sw_interface_add_del_address( + sw_if_index=loopback.sw_if_index, is_add=1, prefix="10.199.0.1/24" + ) + self.assertIn("(10.199.0.1)", self.vapi.cli("show cnat snat-policy")) + @unittest.skipIf("cnat" in config.excluded_plugins, "Exclude CNAT plugin tests") class TestCNatDHCP(CnatCommonTestCase): diff --git a/test/test_flowprobe.py b/test/test_flowprobe.py index 6d9d5c49a3ee..cfddafe649dc 100644 --- a/test/test_flowprobe.py +++ b/test/test_flowprobe.py @@ -68,7 +68,8 @@ def __init__( ): self._test = test self._intf = intf - self._intf_obj = getattr(self._test, intf) + # intf is an attribute name of the test case, or an interface object + self._intf_obj = getattr(self._test, intf) if isinstance(intf, str) else intf self._active = active if passive == 0 or passive < active: self._passive = active + 1 @@ -678,6 +679,24 @@ def test_get_params(self): ipfix.remove_vpp_config() self.logger.info("FFP_TEST_FINISH_0004") + def test_tx_interface_add_del_invalid_which(self): + """the replaced tx message rejects a which that names no variant""" + self.logger.info("FFP_TEST_START_TX_WHICH") + + # the message used to carry a bitmask of forwarding paths; 3 is + # FLOWPROBE_WHICH_FLAG_IP4 | FLOWPROBE_WHICH_FLAG_L2 and names no + # variant, while 5 is past the end of the variant arrays + for which in (3, 5): + with self.subTest(which=which): + with self.vapi.assert_negative_api_retval(): + self.vapi.flowprobe_tx_interface_add_del( + is_add=1, + which=which, + sw_if_index=self.pg0.sw_if_index, + ) + + self.logger.info("FFP_TEST_FINISH_TX_WHICH") + class DatapathTestsHolder(object): """collect information on Ethernet, IP4 and IP6 datapath (no timers)""" @@ -1316,6 +1335,172 @@ class DatapathRx(MethodHolder, DatapathTestsHolder): direction = "rx" +@unittest.skipIf( + "flowprobe" in config.excluded_plugins, "Exclude Flowprobe plugin tests" +) +class DatapathSubInterface(MethodHolder): + """L2 datapath enabled on a VLAN sub-interface (no timers)""" + + def test_L2onSubInterface(self): + """L2, L3 and L4 data on the L2 datapath of a sub-interface""" + self.pg_enable_capture(self.pg_interfaces) + self.pkts = [] + + # a dot1ad sub-interface on a physical port + subif = VppDot1ADSubint(self, self.pg7, 0, 300, 400) + subif.admin_up() + subif.config_ip4() + subif.config_ip6() + + # enable the feature on the sub-interface, not on the physical port + ipfix = VppCFLOW(test=self, intf=subif, layer="l2 l3 l4", direction="rx") + ipfix.add_vpp_config() + + ipfix_decoder = IPFIXDecoder() + tmpl_l2_field_count = TMPL_COMMON_FIELD_COUNT + TMPL_L2_FIELD_COUNT + tmpl_ip_field_count = ( + TMPL_COMMON_FIELD_COUNT + + TMPL_L2_FIELD_COUNT + + TMPL_L3_FIELD_COUNT + + TMPL_L4_FIELD_COUNT + ) + # template 0 is L2 only, 1 is IPv4 and 2 is IPv6 + templates = ipfix.verify_templates( + ipfix_decoder, + count=3, + field_count_in=(tmpl_l2_field_count, tmpl_ip_field_count), + ) + + for ip_ver in ("v4", "v6"): + pkt = Ether(src=subif.remote_mac, dst=self.pg7.local_mac) + if ip_ver == "v4": + src_ip, dst_ip = subif.remote_ip4, "9.0.0.1" + src_ip_id = IPFIX_SRC_IP4_ADDR_ID + dst_ip_id = IPFIX_DST_IP4_ADDR_ID + ethertype = 8 + family = socket.AF_INET + pkt /= IP(src=src_ip, dst=dst_ip) + else: + src_ip, dst_ip = subif.remote_ip6, "2001:db8:9::1" + src_ip_id = 27 + dst_ip_id = 28 + ethertype = 56710 + family = socket.AF_INET6 + pkt /= IPv6(src=src_ip, dst=dst_ip) + pkt /= UDP(sport=1234, dport=4321) + pkt /= Raw(b"\xa5" * 32) + + self.pkts = [subif.add_dot1ad_layer(pkt, 300, 400)] + self.pg7.add_stream(self.pkts) + self.pg_start() + + # make sure the flow we expect actually showed up + self.vapi.ipfix_flush() + cflow = self.wait_for_cflow_packet( + self.collector, templates[1 if ip_ver == "v4" else 2], 15 + ) + data = ipfix_decoder.decode_data_set(cflow.getlayer(Set)) + self.assertEqual(len(data), 1) + record = data[0] + self.assertEqual(len(record), tmpl_ip_field_count) + # the flow is keyed on the sub-interface, not on its parent + self.assertEqual(int(binascii.hexlify(record[10]), 16), subif.sw_if_index) + self.assertEqual(int(binascii.hexlify(record[256]), 16), ethertype) + self.assertEqual(int(binascii.hexlify(record[4]), 16), 17) + self.assertEqual(int(binascii.hexlify(record[7]), 16), 1234) + self.assertEqual(int(binascii.hexlify(record[11]), 16), 4321) + self.assertEqual( + int(binascii.hexlify(record[IPFIX_FLOW_DIRECTION_ID]), 16), 0 + ) + self.assertEqual( + int(binascii.hexlify(record[src_ip_id]), 16), + int(binascii.hexlify(socket.inet_pton(family, src_ip)), 16), + ) + self.assertEqual( + int(binascii.hexlify(record[dst_ip_id]), 16), + int(binascii.hexlify(socket.inet_pton(family, dst_ip)), 16), + ) + + # cleanup, the feature must be disabled before the sub-interface goes away + ipfix.remove_vpp_config() + subif.remove_vpp_config() + + def test_L2onSubInterfaceTx(self): + """L2, L3 and L4 data on the egress side of a sub-interface""" + self.pg_enable_capture(self.pg_interfaces) + self.pkts = [] + + # a dot1ad sub-interface on a physical port; different tags than + # test_L2onSubInterface, so that one leaving its sub-interface behind + # cannot fail this test + subif = VppDot1ADSubint(self, self.pg7, 1, 301, 401) + subif.admin_up() + subif.config_ip4() + subif.configure_ipv4_neighbors() + + # enable the feature on the sub-interface, not on the physical port + ipfix = VppCFLOW(test=self, intf=subif, layer="l2 l3 l4", direction="tx") + ipfix.add_vpp_config() + + ipfix_decoder = IPFIXDecoder() + tmpl_l2_field_count = TMPL_COMMON_FIELD_COUNT + TMPL_L2_FIELD_COUNT + tmpl_ip_field_count = ( + TMPL_COMMON_FIELD_COUNT + + TMPL_L2_FIELD_COUNT + + TMPL_L3_FIELD_COUNT + + TMPL_L4_FIELD_COUNT + ) + templates = ipfix.verify_templates( + ipfix_decoder, + count=3, + field_count_in=(tmpl_l2_field_count, tmpl_ip_field_count), + ) + + # route the traffic out of the sub-interface + route = VppIpRoute( + self, + "9.0.0.0", + 24, + [VppRoutePath(subif.remote_ip4, subif.sw_if_index)], + ) + route.add_vpp_config() + + self.pkts = [ + Ether(src=self.pg8.remote_mac, dst=self.pg8.local_mac) + / IP(src=self.pg8.remote_ip4, dst="9.0.0.1") + / UDP(sport=1234, dport=4321) + / Raw(b"\xa5" * 32) + ] + self.pg8.add_stream(self.pkts) + self.pg_start() + + # make sure the flow we expect actually showed up + self.vapi.ipfix_flush() + cflow = self.wait_for_cflow_packet(self.collector, templates[1], 15) + data = ipfix_decoder.decode_data_set(cflow.getlayer(Set)) + self.assertEqual(len(data), 1) + record = data[0] + self.assertEqual(len(record), tmpl_ip_field_count) + # the flow belongs to the sub-interface the frames left through + self.assertEqual(int(binascii.hexlify(record[14]), 16), subif.sw_if_index) + self.assertEqual(int(binascii.hexlify(record[10]), 16), self.pg8.sw_if_index) + # the ethertype of the tagged frame is the inner one + self.assertEqual(int(binascii.hexlify(record[256]), 16), 8) + self.assertEqual(int(binascii.hexlify(record[4]), 16), 17) + self.assertEqual(int(binascii.hexlify(record[7]), 16), 1234) + self.assertEqual(int(binascii.hexlify(record[11]), 16), 4321) + self.assertEqual(int(binascii.hexlify(record[IPFIX_FLOW_DIRECTION_ID]), 16), 1) + self.assertEqual( + int(binascii.hexlify(record[IPFIX_DST_IP4_ADDR_ID]), 16), + int(binascii.hexlify(socket.inet_pton(socket.AF_INET, "9.0.0.1")), 16), + ) + + # cleanup, the feature must be disabled before the sub-interface goes away + ipfix.remove_vpp_config() + route.remove_vpp_config() + subif.remove_vpp_config() + + @unittest.skipUnless(config.extended, "part of extended tests") @unittest.skipIf( "flowprobe" in config.excluded_plugins, "Exclude Flowprobe plugin tests" diff --git a/test/test_gre.py b/test/test_gre.py index 4ea52701ba88..26b3b586129c 100644 --- a/test/test_gre.py +++ b/test/test_gre.py @@ -6,19 +6,21 @@ from scapy.packet import Raw from scapy.layers.l2 import Ether, Dot1Q, GRE from scapy.layers.inet import IP, UDP -from scapy.layers.inet6 import IPv6 +from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest from scapy.volatile import RandMAC, RandIP from framework import VppTestCase from asfframework import VppTestRunner, tag_fixme_vpp_workers from vpp_sub_interface import L2_VTR_OP, VppDot1QSubint from vpp_gre_interface import VppGreInterface +from vpp_ipsec import VppIpsecSA from vpp_teib import VppTeib from vpp_ip import DpoProto from vpp_ip_route import ( VppIpRoute, VppRoutePath, VppIpTable, + VppIpInterfaceAddress, VppMplsLabel, ) from vpp_mpls_tunnel_interface import VppMPLSTunnelInterface @@ -645,8 +647,15 @@ def test_gre(self): self.pg0.local_ip4, self.pg0.remote_ip4, ) + decap_counter_before = self.statistics.get_err_counter( + "/err/gre4-input/GRE input packets decapsulated" + ) rx = self.send_and_expect(self.pg0, tx, self.pg0) self.verify_decapped_4o4(self.pg0, rx, tx) + decap_counter_after = self.statistics.get_err_counter( + "/err/gre4-input/GRE input packets decapsulated" + ) + self.assertEqual(decap_counter_after, decap_counter_before + len(tx)) # # Send tunneled packets that do not match the tunnel's src @@ -843,6 +852,117 @@ def test_gre_with_key(self): route_via_tun.remove_vpp_config() gre_if.remove_vpp_config() + def test_non_erspan_session_id_is_rejected(self): + tunnel_type = VppEnum.vl_api_gre_tunnel_type_t.GRE_API_TUNNEL_TYPE_L3 + tunnel_mode = VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_P2P + + with self.vapi.assert_negative_api_retval(): + self.vapi.gre_tunnel_add_del_v2( + is_add=1, + tunnel={ + "src": self.pg0.local_ip4, + "dst": "1.1.1.2", + "outer_table_id": 0, + "instance": 0xFFFFFFFF, + "type": tunnel_type, + "mode": tunnel_mode, + "flags": 0, + "session_id": 1, + "key": 0, + }, + ) + + def test_teib_ipv6_peer_over_ipv4_underlay(self): + """a TEIB peer may use a different family than its next hop""" + e = VppEnum.vl_api_tunnel_mode_t + gre_if = VppGreInterface( + self, + self.pg3.local_ip4, + "0.0.0.0", + mode=e.TUNNEL_API_MODE_MP, + ) + gre_if.add_vpp_config() + gre_if.admin_up() + gre_if.config_ip4() + + ll1 = "fe80:1::1" + ll2 = "fe80:2::2" + + VppIpInterfaceAddress(self, gre_if, ll1, 128).add_vpp_config() + + p_echo_request = ( + Ether(src=self.pg3.remote_mac, dst=self.pg3.local_mac) + / IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) + / GRE() + / IPv6(src=ll2, dst=ll1) + / ICMPv6EchoRequest() + ) + + # the tunnel has no peer in the link-local table yet + self.send_and_assert_no_replies(self.pg3, [p_echo_request]) + + # the peer is IPv6 link-local, the next hop is IPv4 + teib = VppTeib(self, gre_if, ll2, self.pg3.remote_ip4) + teib.add_vpp_config() + self.assertTrue(teib.query_vpp_config()) + + self.logger.info(self.vapi.cli("sh ip6-ll %s %s" % (gre_if.name, ll2))) + self.send_and_expect(self.pg3, [p_echo_request], self.pg3) + + # the tunnel is gone, so is its teib entry; the registry removes the + # remaining objects in reverse order + teib.remove_vpp_config() + gre_if.admin_down() + gre_if.remove_vpp_config() + + def test_ipsec_protect_rejects_tunnel_subinterface(self): + """IPsec protect on a sub-interface of a GRE tunnel is rejected""" + + gre_if = VppGreInterface( + self, + self.pg0.local_ip4, + self.pg0.remote_ip4, + type=(VppEnum.vl_api_gre_tunnel_type_t.GRE_API_TUNNEL_TYPE_TEB), + ) + gre_if.add_vpp_config() + gre_if.admin_up() + + # only the tunnel's own interface is in the lookup table, so asking + # about this one must not run off the end of the table + sub_if = VppDot1QSubint(self, gre_if, 500) + + sa_out = VppIpsecSA( + self, + 1, + 100, + VppEnum.vl_api_ipsec_integ_alg_t.IPSEC_API_INTEG_ALG_SHA1_96, + b"b" * 20, + VppEnum.vl_api_ipsec_crypto_alg_t.IPSEC_API_CRYPTO_ALG_AES_CBC_128, + b"a" * 16, + VppEnum.vl_api_ipsec_proto_t.IPSEC_API_PROTO_ESP, + ) + sa_out.add_vpp_config() + + with self.vapi.assert_negative_api_retval(): + self.vapi.ipsec_tunnel_protect_update( + tunnel={ + "sw_if_index": sub_if.sw_if_index, + "n_sa_in": 0, + "sa_out": sa_out.id, + "sa_in": [], + "nh": "0.0.0.0", + } + ) + + # the rejected call may not have recorded a protection for the + # sub-interface either + for entry in self.vapi.ipsec_tunnel_protect_dump(sw_if_index=0xFFFFFFFF): + self.assertNotEqual(entry.tun.sw_if_index, sub_if.sw_if_index) + + sub_if.remove_vpp_config() + gre_if.admin_down() + gre_if.remove_vpp_config() + def test_gre6(self): """GRE IPv6 tunnel Tests""" diff --git a/test/test_ipip.py b/test/test_ipip.py index b92f7c54d07f..c89217bec4ae 100644 --- a/test/test_ipip.py +++ b/test/test_ipip.py @@ -29,14 +29,14 @@ """ -def ipip_add_tunnel(test, src, dst, table_id=0, dscp=0x0, flags=0): +def ipip_add_tunnel(test, src, dst, table_id=0, dscp=0x0, flags=0, instance=0xFFFFFFFF): """Add a IPIP tunnel""" return test.vapi.ipip_add_tunnel( tunnel={ "src": src, "dst": dst, "table_id": table_id, - "instance": 0xFFFFFFFF, + "instance": instance, "dscp": dscp, "flags": flags, } @@ -689,6 +689,116 @@ def test_mipip4(self): ipip_if.unconfig_ip4() ipip_if.set_table_ip4(0) + def test_teib_on_interface_past_tunnel_mapping(self): + """a TEIB entry on an interface past the tunnel mapping is ignored""" + + # + # the mapping from sw_if_index to tunnel grows to cover every index a + # tunnel ever took, so the index equal to its length is the one with no + # slot at all. The loopbacks created first take every free interface + # index, which leaves the tunnel created next at the top of the + # interface pool, and the loopback created after it takes exactly the + # index one past the end of the mapping. The TEIB entry added on that + # loopback reaches the ipip teib callback with such an index. An + # index further past the end is rejected before the read, so the index + # has to be this one. + # + loops = self.create_loopback_interfaces(32) + + tun = VppIpIpTunInterface( + self, + self.pg0, + self.pg0.local_ip4, + "0.0.0.0", + mode=(VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_MP), + ) + tun.add_vpp_config() + self.logger.info("ipip tunnel sw_if_index=%d" % tun.sw_if_index) + + lo = self.create_loopback_interfaces(1)[0] + loops.append(lo) + + # + # the loopback created before the tunnel and the tunnel itself have to + # be consecutive, otherwise a free interface index survived the + # loopbacks, the tunnel did not take the top of the pool, and the + # mapping can already reach past the index of the loopback created + # after it, which would leave the entry below short of the read. + # + self.assertEqual(loops[-2].sw_if_index + 1, tun.sw_if_index) + self.assertEqual(lo.sw_if_index, tun.sw_if_index + 1) + + teib = VppTeib(self, lo, "10.0.0.1", self.pg0.remote_ip4) + teib.add_vpp_config() + self.assertTrue(teib.query_vpp_config()) + + # + # the loopback is not a tunnel, so the tunnel database must still + # hold the one tunnel; before the bound was fixed the lookup ran off + # the end of the mapping and registered a key built from whatever + # followed it. + # + self.assertEqual( + len(self.vapi.cli("show ipip tunnel-hash").strip().splitlines()), 1 + ) + + teib.remove_vpp_config() + for loop in loops: + loop.remove_vpp_config() + tun.remove_vpp_config() + + def test_route_via_mipip_subif(self): + """a route via a sub-interface on a stale tunnel mapping slot""" + + # + # ipip deletes a tunnel by putting the invalid sentinel in its slot + # of the sw_if_index to tunnel mapping and leaving the mapping at its + # length, so the next interface to be created reuses the sentinel's + # sw_if_index. The adjacency of a p2mp tunnel is built through the + # tunnel's update_adjacency callback, which is handed that interface's + # own index; unguarded it dereferenced the sentinel as a pool index. + # + t1 = VppIpIpTunInterface( + self, + self.pg0, + self.pg0.local_ip4, + "0.0.0.0", + mode=(VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_MP), + ) + t1.add_vpp_config() + t1_index = t1.sw_if_index + + t2 = VppIpIpTunInterface( + self, + self.pg0, + "10.9.9.9", + "0.0.0.0", + mode=(VppEnum.vl_api_tunnel_mode_t.TUNNEL_API_MODE_MP), + ) + t2.add_vpp_config() + t2.admin_up() + + t1.remove_vpp_config() + + r = self.vapi.create_vlan_subif(t2.sw_if_index, 100) + sub_index = r.sw_if_index + self.assertEqual(sub_index, t1_index) + + route = VppIpRoute( + self, "10.99.0.0", 24, [VppRoutePath("192.0.2.1", sub_index)] + ) + route.add_vpp_config() + + # + # the interface is not a tunnel, so the next hop stays unresolved and + # the route must be installed as a drop rather than taking the tunnel + # from the slot the index pointed at. + # + self.assertIn("dpo-drop ip4", self.vapi.cli("show ip fib 10.99.0.0/24")) + + route.remove_vpp_config() + t2.remove_vpp_config() + @unittest.skipIf("ipip" in config.excluded_plugins, "Exclude IPIP plugin tests") class TestIPIP6(VppTestCase): @@ -1489,5 +1599,110 @@ def test_mpls(self): tun6.disable_mpls() +@unittest.skipIf("ipip" in config.excluded_plugins, "Exclude IPIP plugin tests") +class TestIPIP6rdTunnelDelete(VppTestCase): + """6RD tunnel delete test case""" + + # src/vnet/error.h: VNET_API_ERROR_INVALID_VALUE + VNET_API_ERROR_INVALID_VALUE = -7 + + def setUp(self): + super(TestIPIP6rdTunnelDelete, self).setUp() + self.ip4_table = VppIpTable(self, 4).add_vpp_config() + self.ip6_table = VppIpTable(self, 6, True).add_vpp_config() + + def add_6rd_tunnel(self): + """Add a 6RD tunnel in the two tables of setUp()""" + return self.vapi.ipip_6rd_add_tunnel( + ip6_table_id=self.ip6_table.table_id, + ip4_table_id=self.ip4_table.table_id, + ip6_prefix="2002::/16", + ip4_prefix="0.0.0.0/0", + ip4_src="10.0.0.1", + security_check=True, + ) + + def assert_ip6_table_freed(self): + """The ip6 table of setUp() is gone once its locks are released""" + self.ip6_table.remove_vpp_config() + self.assertNotIn( + self.ip6_table.table_id, + [t.table.table_id for t in self.vapi.ip_table_dump() if t.table.is_ip6], + ) + + def test_sixrd_del_tunnel_rejects_plain_ipip_tunnel(self): + """delete a plain IPIP tunnel through the 6RD entry point""" + tunnel = ipip_add_tunnel(self, "1.1.1.1", "2.2.2.2", instance=100) + sw_if_index = tunnel.sw_if_index + + with self.vapi.assert_known_api_retval( + retvals=[self.VNET_API_ERROR_INVALID_VALUE] + ): + self.vapi.ipip_6rd_del_tunnel(sw_if_index=sw_if_index) + + self.assertFalse(self.vpp_dead) + tunnels = self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index) + self.assertEqual(1, len(tunnels)) + self.assertEqual(sw_if_index, tunnels[0].tunnel.sw_if_index) + + self.vapi.ipip_del_tunnel(sw_if_index=sw_if_index) + + def test_sixrd_del_tunnel_cli_rejects_plain_ipip_tunnel(self): + """delete a plain IPIP tunnel through the 6RD CLI entry point""" + tunnel = ipip_add_tunnel(self, "3.3.3.3", "4.4.4.4", instance=101) + sw_if_index = tunnel.sw_if_index + + self.vapi.cli("delete 6rd tunnel sw_if_index %d" % sw_if_index) + + self.assertFalse(self.vpp_dead) + tunnels = self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index) + self.assertEqual(1, len(tunnels)) + + self.vapi.ipip_del_tunnel(sw_if_index=sw_if_index) + + def test_sixrd_del_tunnel_removes_6rd_tunnel(self): + """delete a 6RD tunnel through the 6RD entry point""" + tunnel = self.add_6rd_tunnel() + sw_if_index = tunnel.sw_if_index + + self.vapi.ipip_6rd_del_tunnel(sw_if_index=sw_if_index) + + self.assertEqual([], self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index)) + self.assert_ip6_table_freed() + + def test_ipip_del_tunnel_rejects_6rd_tunnel(self): + """delete a 6RD tunnel through the generic IPIP entry point""" + tunnel = self.add_6rd_tunnel() + sw_if_index = tunnel.sw_if_index + + with self.vapi.assert_known_api_retval( + retvals=[self.VNET_API_ERROR_INVALID_VALUE] + ): + self.vapi.ipip_del_tunnel(sw_if_index=sw_if_index) + + self.assertFalse(self.vpp_dead) + tunnels = self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index) + self.assertEqual(1, len(tunnels)) + self.assertEqual(sw_if_index, tunnels[0].tunnel.sw_if_index) + + self.vapi.ipip_6rd_del_tunnel(sw_if_index=sw_if_index) + self.assertEqual([], self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index)) + self.assert_ip6_table_freed() + + def test_ipip_del_tunnel_cli_rejects_6rd_tunnel(self): + """delete a 6RD tunnel through the generic IPIP CLI entry point""" + tunnel = self.add_6rd_tunnel() + sw_if_index = tunnel.sw_if_index + + self.vapi.cli("delete ipip tunnel sw_if_index %d" % sw_if_index) + + self.assertFalse(self.vpp_dead) + tunnels = self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index) + self.assertEqual(1, len(tunnels)) + + self.vapi.ipip_6rd_del_tunnel(sw_if_index=sw_if_index) + self.assertEqual([], self.vapi.ipip_tunnel_dump(sw_if_index=sw_if_index)) + + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner) diff --git a/test/test_ipsec_api.py b/test/test_ipsec_api.py index 11dc4dd0c9c8..3310bc080750 100644 --- a/test/test_ipsec_api.py +++ b/test/test_ipsec_api.py @@ -44,6 +44,37 @@ def __sa_dump(self, sa): return dump self.fail("SA not found in VPP") + def test_rejected_protect_gives_the_sa_back(self): + """a rejected tunnel protection must not pin the SA""" + sa_out = VppIpsecSA( + self, + self.ipv4_params.scapy_tun_sa_id, + self.ipv4_params.scapy_tun_spi, + self.ipv4_params.auth_algo_vpp_id, + self.ipv4_params.auth_key, + self.ipv4_params.crypt_algo_vpp_id, + self.ipv4_params.crypt_key, + self.vpp_esp_protocol, + ) + sa_out.add_vpp_config() + + # pg0's device class protects no tunnel, so this is refused + with self.vapi.assert_negative_api_retval(): + self.vapi.ipsec_tunnel_protect_update( + tunnel={ + "sw_if_index": self.pg0.sw_if_index, + "n_sa_in": 0, + "sa_out": sa_out.id, + "sa_in": [], + "nh": "0.0.0.0", + } + ) + + # the refused call must have released the SA, or it stays locked and + # can never be deleted again + sa_out.remove_vpp_config() + self.assertFalse(sa_out.query_vpp_config()) + def test_sa_basic(self): """basic SA API tests""" sa = VppIpsecSA( diff --git a/test/test_linux_cp.py b/test/test_linux_cp.py index 8e24e05c908d..79fec3b71db7 100644 --- a/test/test_linux_cp.py +++ b/test/test_linux_cp.py @@ -1,12 +1,14 @@ #!/usr/bin/env python3 import unittest +import os import socket +import subprocess from ipaddress import ip_address, ip_interface from scapy.layers.inet import IP, UDP from scapy.layers.inet6 import IPv6, Raw -from scapy.layers.l2 import Ether, ARP, Dot3, LLC +from scapy.layers.l2 import Ether, ARP, Dot3, LLC, GRE from scapy.contrib.lacp import LACP from scapy.contrib.lldp import ( LLDPDUChassisID, @@ -37,7 +39,8 @@ ) from test_ipsec_tun_if_esp import TemplateIpsecItf4 from config import config -from vpp_ip_route import FibPathType +from vpp_ip_route import FibPathType, VppRoutePath +from vpp_gre_interface import VppGreInterface from vpp_qemu_utils import ( add_namespace_route, add_namespace_multipath_route, @@ -966,6 +969,8 @@ def _prefix_tuple(prefix): class TestLinuxCPSync(TestLinuxCPNetNSBase): """Linux CP VPP-to-Linux State Sync""" + VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE = -60 + # Addresses assigned to loop0 in the IPv4/IPv6 address sync tests. loop0_prefixes_v4 = ["10.10.1.2/24", "10.10.2.2/24"] loop0_prefixes_v6 = ["2001:db8:1::2/64", "2001:db8:2::2/64"] @@ -995,6 +1000,13 @@ def _del_addr(self, sw_if_index, prefix): sw_if_index=sw_if_index, prefix=prefix, is_add=0 ) + def _vpp_has_ipv4_address(self, sw_if_index, prefix): + """Return whether VPP reports an IPv4 address on an interface.""" + return any( + str(address.prefix) == prefix + for address in self.vapi.ip_address_dump(sw_if_index, is_ipv6=False) + ) + def test_lcp_sync_admin_state(self): """VPP admin state changes sync to Linux""" # Loopbacks default to admin-down, so the host side should already @@ -1118,6 +1130,91 @@ def test_lcp_sync_ipv4_addr(self): # Cleanup loop0.admin_down() + def test_lcp_sync_ipv4_addr_delete_already_absent(self): + """Deleting a VPP address after Linux removed it keeps state converged""" + loop0 = self.lo_interfaces[0] + prefix = "10.40.1.2/24" + prefix_tuple = _prefix_tuple(prefix) + + try: + loop0.admin_up() + self.poll_for( + "hloop0 up", lambda: is_interface_up(self.ns_name, "hloop0"), True + ) + + self._add_addr(loop0.sw_if_index, prefix) + self.poll_for( + f"hloop0 has {prefix}", + lambda: prefix_tuple + in get_interface_addresses(self.ns_name, "hloop0", family="inet"), + True, + ) + + del_namespace_address(self.ns_name, "hloop0", prefix) + self.poll_for( + f"hloop0 no longer has {prefix}", + lambda: prefix_tuple + not in get_interface_addresses(self.ns_name, "hloop0", family="inet"), + True, + ) + # The Linux->VPP sync may already have removed the address. + with self.vapi.assert_known_api_retval( + [0, self.VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE] + ): + self._del_addr(loop0.sw_if_index, prefix) + self.poll_for( + f"VPP no longer has {prefix}", + lambda: not self._vpp_has_ipv4_address(loop0.sw_if_index, prefix), + True, + ) + self.assertNotIn( + prefix_tuple, + get_interface_addresses(self.ns_name, "hloop0", family="inet"), + ) + finally: + if self._vpp_has_ipv4_address(loop0.sw_if_index, prefix): + self._del_addr(loop0.sw_if_index, prefix) + loop0.admin_down() + + def test_lcp_sync_ipv4_addr_external_delete_preserves_other_state(self): + """Removing one Linux address does not sweep another valid VPP address""" + loop0 = self.lo_interfaces[0] + prefixes = ("10.40.3.2/24", "10.40.4.2/24") + prefix_tuples = tuple(_prefix_tuple(prefix) for prefix in prefixes) + + try: + loop0.admin_up() + self.poll_for( + "hloop0 up", lambda: is_interface_up(self.ns_name, "hloop0"), True + ) + for prefix in prefixes: + self._add_addr(loop0.sw_if_index, prefix) + self.poll_for( + "hloop0 has both ipv4 addresses", + lambda: sorted( + get_interface_addresses(self.ns_name, "hloop0", family="inet") + ), + sorted(prefix_tuples), + ) + + del_namespace_address(self.ns_name, "hloop0", prefixes[0]) + self.poll_for( + f"VPP removed {prefixes[0]}", + lambda: not self._vpp_has_ipv4_address(loop0.sw_if_index, prefixes[0]), + True, + ) + + self.assertTrue(self._vpp_has_ipv4_address(loop0.sw_if_index, prefixes[1])) + self.assertEqual( + sorted(get_interface_addresses(self.ns_name, "hloop0", family="inet")), + [prefix_tuples[1]], + ) + finally: + for prefix in prefixes: + if self._vpp_has_ipv4_address(loop0.sw_if_index, prefix): + self._del_addr(loop0.sw_if_index, prefix) + loop0.admin_down() + def test_lcp_sync_ipv6_addr(self): """VPP IPv6 address changes sync to Linux""" loop0 = self.lo_interfaces[0] @@ -1503,6 +1600,223 @@ def test_pair_create_delete_tun(self): False, ) + @unittest.skipIf( + "linux-cp" in config.excluded_plugins, "Exclude linux-cp plugin tests" + ) + @unittest.skipIf("gre" in config.excluded_plugins, "Exclude GRE plugin tests") + def test_gre_lcp_ipv4_survives_pair_churn(self): + self.create_pg_interfaces(range(1)) + underlay = self.pg_interfaces[0] + gre_if = VppGreInterface(self, underlay.local_ip4, underlay.remote_ip4) + sender = None + capture_active = False + gre_created = False + route_created = False + pair_created = False + linux_route_present = False + ready_file = os.path.join(self.tempdir, "gre_lcp_sender_ready") + go_file = os.path.join(self.tempdir, "gre_lcp_sender_go") + inner_dst = "198.18.0.1" + + try: + underlay.admin_up() + underlay.config_ip4() + underlay.resolve_arp() + + gre_if.add_vpp_config() + gre_created = True + gre_if.admin_up() + self.vapi.cli( + f"lcp create {gre_if.name} host-if hgre0 netns {self.ns_name} tun" + ) + pair_created = True + self.poll_for( + "GRE LCP pair exists", + lambda: self._find_lcp_pair(gre_if.sw_if_index) is not None, + True, + ) + pair = self._find_lcp_pair(gre_if.sw_if_index) + self.assertIsNotNone(pair) + self.assertEqual(pair.host_if_type, 1, "GRE LCP pair should use TUN") + + gre_if.config_ip4() + route = VppIpRoute( + self, + inner_dst, + 32, + [VppRoutePath("0.0.0.0", gre_if.sw_if_index)], + ) + route.add_vpp_config() + route_created = True + expected_path = dict( + type=FibPathType.FIB_PATH_TYPE_NORMAL, + sw_if_index=gre_if.sw_if_index, + ) + self.verify_paths(f"{inner_dst}/32", expected_path) + expected_address = [_prefix_tuple(gre_if.local_ip4_prefix)] + self.poll_for( + "hgre0 has GRE IPv4 address", + lambda: get_interface_addresses(self.ns_name, "hgre0", family="inet"), + expected_address, + ) + add_namespace_route(self.ns_name, f"{inner_dst}/32", dev="hgre0") + linux_route_present = True + + sender_script = """ +import os +import sys +import time +from scapy.error import Scapy_Exception +from scapy.all import IP, Raw, UDP, send + +iface, source, destination, ready, go = sys.argv[1:] +def send_packet(sequence): + try: + send(IP(src=source, dst=destination) / UDP(sport=1234, dport=1234) / Raw(str(sequence).encode()), iface=iface, verbose=0) + except (OSError, Scapy_Exception, ValueError): + pass + +for sequence in range(20): + send_packet(sequence) +open(ready, "w").close() +sequence = 20 +while not os.path.exists(go): + send_packet(sequence) + sequence += 1 + time.sleep(0.01) +for sequence in range(100, 130): + send_packet(sequence) + time.sleep(0.01) +""" + underlay.enable_capture() + capture_active = True + sender = subprocess.Popen( + [ + "ip", + "netns", + "exec", + self.ns_name, + "python3", + "-c", + sender_script, + "hgre0", + gre_if.local_ip4, + inner_dst, + ready_file, + go_file, + ], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + self.poll_for( + "inner IPv4 sender is active", + lambda: os.path.exists(ready_file), + True, + ) + self.assertIsNone(self.vpp.poll(), "VPP must be alive before churn") + + gre_if.admin_down() + self.poll_for( + "hgre0 down after GRE admin_down", + lambda: is_interface_up(self.ns_name, "hgre0"), + False, + ) + gre_if.admin_up() + self.poll_for( + "hgre0 up after GRE admin_up", + lambda: is_interface_up(self.ns_name, "hgre0"), + True, + ) + + self.vapi.cli(f"lcp delete {gre_if.name}") + pair_created = False + self.poll_for( + "GRE LCP pair removed", + lambda: self._find_lcp_pair(gre_if.sw_if_index) is not None, + False, + ) + self.poll_for( + "hgre0 removed after pair delete", + lambda: interface_exists(self.ns_name, "hgre0"), + False, + ) + del_namespace_route(self.ns_name, f"{inner_dst}/32", check=False) + linux_route_present = False + self.assertIsNone(self.vpp.poll(), "VPP must be alive after pair delete") + + self.vapi.cli( + f"lcp create {gre_if.name} host-if hgre0 netns {self.ns_name} tun" + ) + pair_created = True + self.poll_for( + "GRE LCP pair recreated", + lambda: self._find_lcp_pair(gre_if.sw_if_index) is not None, + True, + ) + self.poll_for( + "hgre0 recreated", + lambda: interface_exists(self.ns_name, "hgre0"), + True, + ) + self.poll_for( + "hgre0 has GRE IPv4 address after recreation", + lambda: get_interface_addresses(self.ns_name, "hgre0", family="inet"), + expected_address, + ) + add_namespace_route(self.ns_name, f"{inner_dst}/32", dev="hgre0") + linux_route_present = True + self.verify_paths(f"{inner_dst}/32", expected_path) + self.assertTrue(gre_if.query_vpp_config()) + self.assertIn(gre_if.name, self.vapi.cli("show adj")) + self.assertIsNone( + self.vpp.poll(), "VPP must be alive after pair recreation" + ) + + with open(go_file, "w"): + pass + sender.wait(timeout=10) + self.assertEqual(sender.returncode, 0, "inner IPv4 sender failed") + + def _capture_inner_sequences(): + captured = underlay._get_capture() + if captured is None: + return [] + sequences = [] + for packet in captured: + if packet.haslayer(GRE): + inner = IP(bytes(packet[GRE].payload)) + if inner.dst == inner_dst and inner.haslayer(Raw): + sequences.append(int(bytes(inner[Raw].load))) + return sequences + + self.poll_for( + "capture has pre- and post-churn inner packets", + lambda: any(sequence < 20 for sequence in _capture_inner_sequences()) + and any(sequence >= 100 for sequence in _capture_inner_sequences()), + True, + timeout=10, + ) + capture_active = False + inner_sequences = _capture_inner_sequences() + self.assertTrue(any(sequence < 20 for sequence in inner_sequences)) + self.assertTrue(any(sequence >= 100 for sequence in inner_sequences)) + finally: + if sender is not None and sender.poll() is None: + sender.terminate() + sender.wait(timeout=5) + if capture_active: + underlay.disable_capture() + if pair_created: + self.vapi.cli(f"lcp delete {gre_if.name}") + if linux_route_present: + del_namespace_route(self.ns_name, f"{inner_dst}/32", check=False) + if route_created: + route.remove_vpp_config() + if gre_created: + gre_if.remove_vpp_config() + underlay.unconfig_ip4() + underlay.admin_down() + def test_pair_create_delete_tap(self): """Create and delete TAP LCP pair on pg interface""" self.create_pg_interfaces(range(1)) diff --git a/test/test_pppoe.py b/test/test_pppoe.py index b0ea5cfe4f4e..1c0f959d9df3 100644 --- a/test/test_pppoe.py +++ b/test/test_pppoe.py @@ -645,6 +645,105 @@ def test_PPPoE_Encap_Multiple(self): # Delete a route that resolves the server's destination route_sever_dst.remove_vpp_config() + def test_route_via_pppoe_subif(self): + """a route via a sub-interface created on a PPPoE session""" + + # + # the mapping from sw_if_index to session grows to cover the index of + # every session, so the index equal to its length is the one with no + # slot at all. The loopbacks take every free interface index left by + # the earlier test cases, the sessions below take the indices those + # cases released until one of them is given a new index above the + # loopbacks, and the sub-interface created on that session takes + # exactly the index one past the end of the mapping. The adjacency + # code resolves the interface class of a sub-interface to the class of + # its super interface but hands the callbacks the sub-interface's own + # index, so the route below reaches them with that index. + # + loops = self.create_loopback_interfaces(32) + + # Send PPPoE Discovery + tx0 = self.create_stream_pppoe_discovery( + self.pg0, self.pg1, self.pg0.remote_mac + ) + self.pg0.add_stream(tx0) + self.pg_start() + + # + # a session deleted by an earlier test case leaves its interface index + # on the free list, and the next session takes that index back instead + # of a new one, so keep adding sessions until one lands past the last + # loopback and the session below is at the top of the interface pool. + # + sessions = [] + for client_session_id in range(self.session_id, self.session_id + 64): + session = VppPppoeInterface( + self, self.pg0.remote_ip4, self.pg0.remote_mac, client_session_id + ) + session.add_vpp_config() + sessions.append(session) + if session.sw_if_index > loops[-1].sw_if_index: + break + pppoe_if = sessions[-1] + self.logger.info("pppoe session sw_if_index=%d" % pppoe_if.sw_if_index) + + # + # the session has to be the interface right after the last loopback, + # otherwise the mapping can already reach past its index and the + # sub-interface below never leaves the end of it. + # + self.assertEqual(loops[-1].sw_if_index + 1, pppoe_if.sw_if_index) + + r = self.vapi.create_vlan_subif(pppoe_if.sw_if_index, 100) + sub_index = r.sw_if_index + self.logger.info("pppoe session sub-interface sw_if_index=%d" % sub_index) + self.assertEqual(sub_index, pppoe_if.sw_if_index + 1) + + # + # the sub-interface is created down, and a route through a down + # interface is a drop whatever the session lookup returns, so the + # interface has to be up for the lookup to decide the outcome. + # + self.vapi.sw_interface_set_flags(sub_index, 1) + + route = VppIpRoute( + self, "10.99.0.0", 24, [VppRoutePath("192.0.2.1", sub_index)] + ) + route.add_vpp_config() + + # + # the sub-interface is not a session, so its adjacency must not be + # given the PPPoE rewrite of one. With the lookup unbounded the + # callback read the slot one past the end of the mapping, then installed + # that session's rewrite on this route. + # + fib = self.vapi.cli("show ip fib 10.99.0.0/24") + self.logger.info(fib) + self.assertIn("10.99.0.0/24 fib:0", fib) + self.assertIn("oper-flags:resolved", fib) + self.assertIn("arp-ipv4: via 0.0.0.0", fib) + self.assertNotIn("stacked-on:", fib) + + # + # a packet routed through the sub-interface must not leave encapsulated + # for a session either, and the trace shows it stopping in the ARP node. + # + self.vapi.cli("clear trace") + self.pg1.add_stream( + self.create_stream_ip4(self.pg1, self.pg0, "10.99.0.1", "10.99.0.2") + ) + self.pg_start() + trace = self.vapi.cli("show trace") + self.logger.info(trace) + self.assertIn("ip4-arp", trace) + + route.remove_vpp_config() + self.vapi.delete_subif(sub_index) + for session in reversed(sessions): + session.remove_vpp_config() + for loop in loops: + loop.remove_vpp_config() + if __name__ == "__main__": unittest.main(testRunner=VppTestRunner) diff --git a/test/test_span.py b/test/test_span.py index e25e844eafa5..f25f1f611911 100644 --- a/test/test_span.py +++ b/test/test_span.py @@ -15,6 +15,7 @@ from vpp_vxlan_tunnel import VppVxlanTunnel from collections import namedtuple from vpp_papi import VppEnum +from vpp_papi_exceptions import UnexpectedApiReturnValueError from config import config Tag = namedtuple("Tag", ["dot1", "vlan"]) @@ -208,6 +209,24 @@ def test_device_span(self): self.verify_capture(pg1_pkts, pg2_pkts) + def test_span_api_invalid_index(self): + """SPAN API rejects an invalid sw_if_index from/to""" + + # larger than any interface, and deliberately not the ~0 sentinel + invalid_sw_if_index = 0xFFFFFFFE + + with self.assertRaises(UnexpectedApiReturnValueError) as e: + self.vapi.sw_interface_span_enable_disable( + invalid_sw_if_index, self.pg2.sw_if_index + ) + self.assertEqual(e.exception.retval, -2) # VNET_API_ERROR_INVALID_SW_IF_INDEX + + with self.assertRaises(UnexpectedApiReturnValueError) as e: + self.vapi.sw_interface_span_enable_disable( + self.pg0.sw_if_index, invalid_sw_if_index + ) + self.assertEqual(e.exception.retval, -2) + def test_span_l2_rx(self): """SPAN l2 rx mirror""" @@ -334,6 +353,91 @@ def decap(p): self.verify_capture(pg1_pkts, pg2_decaped) + def test_span_l2_rx_dst_two_gre_erspan_share_sequence(self): + """SPAN l2 rx mirror into two gre-erspan sharing a sequence object""" + + self.sub_if.admin_up() + + gre_if1 = VppGreInterface( + self, + self.pg2.local_ip4, + self.pg2.remote_ip4, + session=543, + type=(VppEnum.vl_api_gre_tunnel_type_t.GRE_API_TUNNEL_TYPE_ERSPAN), + ) + gre_if2 = VppGreInterface( + self, + self.pg2.local_ip4, + self.pg2.remote_ip4, + session=544, + type=(VppEnum.vl_api_gre_tunnel_type_t.GRE_API_TUNNEL_TYPE_ERSPAN), + ) + + gre_if1.add_vpp_config() + gre_if2.add_vpp_config() + gre_if1.admin_up() + gre_if2.admin_up() + self.bridge(gre_if1.sw_if_index) + self.bridge(gre_if2.sw_if_index) + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index) + + pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes, do_dot1=True)[:4] + self.pg0.add_stream(pkts) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_if1.sw_if_index, is_l2=1 + ) + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_if2.sw_if_index, is_l2=1 + ) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.pg1.get_capture(len(pkts)) + mirrored = self.pg2.get_capture(2 * len(pkts)) + sessions = {packet[ERSPAN].session_id for packet in mirrored} + self.assertEqual(sessions, {543, 544}) + + sequence_numbers = [] + for packet in mirrored: + self.decap_erspan(packet, packet[ERSPAN].session_id) + gre_bytes = bytes(packet[GRE]) + self.assertEqual(int.from_bytes(gre_bytes[0:2], "big") & 0x1000, 0x1000) + sequence_numbers.append(int.from_bytes(gre_bytes[4:8], "big")) + + self.assertEqual( + sorted(sequence_numbers), + list(range(min(sequence_numbers), min(sequence_numbers) + len(mirrored))), + ) + + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_if1.sw_if_index, state=0, is_l2=1 + ) + self.bridge(gre_if1.sw_if_index, is_add=0) + gre_if1.remove_vpp_config() + + pkts = self.create_stream(self.pg0, self.pg_if_packet_sizes, do_dot1=True)[:2] + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.pg1.get_capture(len(pkts)) + mirrored = self.pg2.get_capture(len(pkts)) + self.assertEqual({packet[ERSPAN].session_id for packet in mirrored}, {544}) + remaining_sequence_numbers = [] + for packet in mirrored: + self.decap_erspan(packet, 544) + gre_bytes = bytes(packet[GRE]) + remaining_sequence_numbers.append(int.from_bytes(gre_bytes[4:8], "big")) + self.assertTrue(min(remaining_sequence_numbers) > max(sequence_numbers)) + + self.vapi.sw_interface_span_enable_disable( + self.sub_if.sw_if_index, gre_if2.sw_if_index, state=0, is_l2=1 + ) + self.bridge(gre_if2.sw_if_index, is_add=0) + gre_if2.remove_vpp_config() + self.xconnect(self.sub_if.sw_if_index, self.pg1.sw_if_index, is_add=0) + def test_span_l2_rx_dst_gre_subif_vtr(self): """SPAN l2 rx mirror into gre-subif+vtr"""